// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 动作图执行。

package work

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"internal/buildcfg"
	exec "internal/execabs"
	"internal/lazyregexp"
	"io"
	"io/fs"
	"log"
	"math/rand"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"

	"cmd/go/internal/base"
	"cmd/go/internal/cache"
	"cmd/go/internal/cfg"
	"cmd/go/internal/fsys"
	"cmd/go/internal/load"
	"cmd/go/internal/modload"
	"cmd/go/internal/str"
	"cmd/go/internal/trace"
	"cmd/internal/quoted"
	"cmd/internal/sys"
)

// actionList返回在根目录下的dag中的操作列表
// 在深度优先后序遍历中访问的操作列表。
func actionList(root *Action) []*Action {
	seen := map[*Action]bool{}
	all := []*Action{}
	var walk func(*Action)
	walk = func(a *Action) {
		if seen[a] {
			return
		}
		seen[a] = true
		for _, a1 := range a.Deps {
			walk(a1)
		}
		all = append(all, a)
	}
	walk(root)
	return all
}

// do运行根目录下的动作图。
func (b *Builder) Do(ctx context.Context, root *Action) {
	ctx, span := trace.StartSpan(ctx, "exec.Builder.Do ("+root.Mode+" "+root.Target+")")
	defer span.Done()

	if !b.IsCmdList {
		// 如果我们正在做真正的工作，那么在最后花点时间修剪缓存。
		c := cache.Default()
		defer c.Trim()
	}

	// 构建所有操作的列表，分配深度优先的后序优先级。
	// 这里最初的实现是一个真正的队列
	// （使用一个通道），但它的效果是让
	// 被低级别的叶操作分心，而不利于
	// 完成更高级别的操作。
	// 工作顺序对总体执行时间没有多大影响，
	// 但在运行“go test std”时，很高兴看到每个测试
	// 尽快得到结果。分配的优先级
	// 确保在所有其他条件相同的情况下，执行优先于
	// 以简单的深度优先
	// 依赖顺序遍历的方式首先执行。
	all := actionList(root)
	for i, a := range all {
		a.priority = i
	}

	// 写下动作图，没有时间信息，以防我们失败并提前退出。
	writeActionGraph := func() {
		if file := cfg.DebugActiongraph; file != "" {
			if strings.HasSuffix(file, ".go") {
				// 不要覆盖以下中的Go源代码：
				// Go build-debug actiongraph x.Go 
				base.Fatalf("go: refusing to write action graph to %v\n", file)
			}
			js := actionGraphJSON(root)
			if err := os.WriteFile(file, []byte(js), 0666); err != nil {
				fmt.Fprintf(os.Stderr, "go: writing action graph: %v\n", err)
				base.SetExitStatus(1)
			}
		}
	}
	writeActionGraph()

	b.readySema = make(chan bool, len(all))

	// 按操作执行状态初始化。
	for _, a := range all {
		for _, a1 := range a.Deps {
			a1.triggers = append(a1.triggers, a)
		}
		a.pending = len(a.Deps)
		if a.pending == 0 {
			b.ready.push(a)
			b.readySema <- true
		}
	}

	// Handle运行一个操作，并负责触发
	// 因此可以运行的任何操作。
	handle := func(ctx context.Context, a *Action) {
		if a.json != nil {
			a.json.TimeStart = time.Now()
		}
		var err error
		if a.Func != nil && (!a.Failed || a.IgnoreFail) {
			// TODO（matloob）：更好的操作描述
			desc := "Executing action "
			if a.Package != nil {
				desc += "(" + a.Mode + " " + a.Package.Desc() + ")"
			}
			ctx, span := trace.StartSpan(ctx, desc)
			a.traceSpan = span
			for _, d := range a.Deps {
				trace.Flow(ctx, d.traceSpan, a.traceSpan)
			}
			err = a.Func(b, ctx, a)
			span.Done()
		}
		if a.json != nil {
			a.json.TimeDone = time.Now()
		}

		// 这些操作并行运行，但对
		// 共享工作状态的所有更新都通过b.exec序列化。
		b.exec.Lock()
		defer b.exec.Unlock()

		if err != nil {
			if err == errPrintedOutput {
				base.SetExitStatus(2)
			} else {
				base.Errorf("%s", err)
			}
			a.Failed = true
		}

		for _, a0 := range a.triggers {
			if a.Failed {
				a0.Failed = true
			}
			if a0.pending--; a0.pending == 0 {
				b.ready.push(a0)
				b.readySema <- true
			}
		}

		if a == root {
			close(b.readySema)
		}
	}

	var wg sync.WaitGroup

	// 根据并行性启动goroutines。
	// 如果我们使用-n标志（仅打印命令）
	// 将并行度降低到1，这两种方法都会使输出
	// 具有确定性，而且因为根本没有实际的功。
	par := cfg.BuildP
	if cfg.BuildN {
		par = 1
	}
	for i := 0; i < par; i++ {
		wg.Add(1)
		go func() {
			ctx := trace.StartGoroutine(ctx)
			defer wg.Done()
			for {
				select {
				case _, ok := <-b.readySema:
					if !ok {
						return
					}
					// 从b.readySema接收值使
					// 我们有权从就绪队列中获取。
					b.exec.Lock()
					a := b.ready.pop()
					b.exec.Unlock()
					handle(ctx, a)
				case <-base.Interrupted:
					base.SetExitStatus(1)
					return
				}
			}
		}()
	}

	wg.Wait()

	// 再次编写动作图，这次是计时信息。
	writeActionGraph()
}

// buildActionID计算生成操作的操作ID。
func (b *Builder) buildActionID(a *Action) cache.ActionID {
	p := a.Package
	h := cache.NewHash("build " + p.ImportPath)

	// 独立于编译器工具链的配置。
	// 注意：buildmode已在buildGcflags 
	// 中说明，不应显式插入。大多数构建模式使用
	// 相同的编译器设置，可以重用彼此的结果。wen jian defg
	fmt.Fprintf(h, "compile\n")

	// 包括有关包的来源的信息，
	// 可能嵌入到目标文件的调试信息中。
	if cfg.BuildTrimpath {
		// 当-trimpath与从模块缓存构建的包一起使用时，
		// 其调试信息指的是模块路径和版本
		// 而不是目录。
		if p.Module != nil {
			fmt.Fprintf(h, "module %s@%s\n", p.Module.Path, p.Module.Version)
		}
	} else if p.Goroot {
		// Go编译器在GOROOT中构建东西时总是隐藏$GOROOT的确切值
		// 。
		// 
		// C编译器没有，但对于GOROOT中的包，我们重写路径
		// ，就像设置了-trimpath一样，这样在更改
		// （尤其是任何预编译的C存档文件）无效。（请参阅https:
		// GOROOT\u FINAL时，我们不会使构建缓存
		// 
		// b.WorkDir总是被修剪或重写为
		// 文本字符串“/tmp/go build”.
	} else if !strings.HasPrefix(p.Dir, b.WorkDir) {
		// -未设置trimpath，且没有其他重写规则适用，
		// 因此目标文件可能引用包含该包的绝对目录
		// 。
		fmt.Fprintf(h, "dir %s\n", p.Dir)
	}

	if p.Module != nil {
		fmt.Fprintf(h, "go %s\n", p.Module.GoVersion)
	}
	fmt.Fprintf(h, "goos %s goarch %s\n", cfg.Goos, cfg.Goarch)
	fmt.Fprintf(h, "import %q\n", p.ImportPath)
	fmt.Fprintf(h, "omitdebug %v standard %v local %v prefix %q\n", p.Internal.OmitDebug, p.Standard, p.Internal.Local, p.Internal.LocalPrefix)
	if cfg.BuildTrimpath {
		fmt.Fprintln(h, "trimpath")
	}
	if p.Internal.ForceLibrary {
		fmt.Fprintf(h, "forcelibrary\n")
	}
	if len(p.CgoFiles)+len(p.SwigFiles)+len(p.SwigCXXFiles) > 0 {
		fmt.Fprintf(h, "cgo %q\n", b.toolID("cgo"))
		cppflags, cflags, cxxflags, fflags, ldflags, _ := b.CFlags(p)

		ccExe := b.ccExe()
		fmt.Fprintf(h, "CC=%q %q %q %q\n", ccExe, cppflags, cflags, ldflags)
		// 包含C编译器工具ID，这样，如果C 
		// 编译器发生更改，我们将重建包。
		// 但不要对标准库包（如net、
		// ）这样做，以便预构建。Go binary安装
		// 中的文件不需要使用本地编译器重建。
		if !p.Standard {
			if ccID, err := b.gccToolID(ccExe[0], "c"); err == nil {
				fmt.Fprintf(h, "CC ID=%q\n", ccID)
			}
		}
		if len(p.CXXFiles)+len(p.SwigCXXFiles) > 0 {
			cxxExe := b.cxxExe()
			fmt.Fprintf(h, "CXX=%q %q\n", cxxExe, cxxflags)
			if cxxID, err := b.gccToolID(cxxExe[0], "c++"); err == nil {
				fmt.Fprintf(h, "CXX ID=%q\n", cxxID)
			}
		}
		if len(p.FFiles) > 0 {
			fcExe := b.fcExe()
			fmt.Fprintf(h, "FC=%q %q\n", fcExe, fflags)
			if fcID, err := b.gccToolID(fcExe[0], "f95"); err == nil {
				fmt.Fprintf(h, "FC ID=%q\n", fcID)
			}
		}
		// TODO（rsc）：我们应该包括SWIG版本吗？
	}
	if p.Internal.CoverMode != "" {
		fmt.Fprintf(h, "cover %q %q\n", p.Internal.CoverMode, b.toolID("cover"))
	}
	if p.Internal.FuzzInstrument {
		if fuzzFlags := fuzzInstrumentFlags(); fuzzFlags != nil {
			fmt.Fprintf(h, "fuzz %q\n", fuzzFlags)
		}
	}
	fmt.Fprintf(h, "modinfo %q\n", p.Internal.BuildInfo)

	// 特定于编译器工具链的配置。
	switch cfg.BuildToolchainName {
	default:
		base.Fatalf("buildActionID: unknown build toolchain %q", cfg.BuildToolchainName)
	case "gc":
		fmt.Fprintf(h, "compile %s %q %q\n", b.toolID("compile"), forcedGcflags, p.Internal.Gcflags)
		if len(p.SFiles) > 0 {
			fmt.Fprintf(h, "asm %q %q %q\n", b.toolID("asm"), forcedAsmflags, p.Internal.Asmflags)
		}

		// GOARM、GOMIPS等。
		key, val := cfg.GetArchEnv()
		fmt.Fprintf(h, "%s=%s\n", key, val)

		if goexperiment := buildcfg.GOEXPERIMENT(); goexperiment != "" {
			fmt.Fprintf(h, "GOEXPERIMENT=%q\n", goexperiment)
		}

		// TODO（rsc）：说服编译器团队不要添加更多神奇的环境变量，
		// 或者限制传递给子进程的环境变量。
		// 因为这些是笨拙的、未记录的特例黑客
		// 为了调试编译器，它们不能使用'go env-w'进行设置，
		// 所以这里我们使用操作系统。Getenv，不是cfg。Getenv。
		magic := []string{
			"GOCLOBBERDEADHASH",
			"GOSSAFUNC",
			"GOSSADIR",
			"GOSSAHASH",
		}
		for _, env := range magic {
			if x := os.Getenv(env); x != "" {
				fmt.Fprintf(h, "magic %s=%s\n", env, x)
			}
		}
		if os.Getenv("GOSSAHASH") != "" {
			for i := 0; ; i++ {
				env := fmt.Sprintf("GOSSAHASH%d", i)
				x := os.Getenv(env)
				if x == "" {
					break
				}
				fmt.Fprintf(h, "magic %s=%s\n", env, x)
			}
		}
		if os.Getenv("GSHS_LOGFILE") != "" {
			// 笨拙的黑客。编译器写入这个日志文件
			// 所以根本不允许使用缓存。
			// 我们仍将写入缓存，但它将是
			// 基本上无法读取的。
			fmt.Fprintf(h, "nocache %d\n", time.Now().UnixNano())
		}

	case "gccgo":
		id, err := b.gccToolID(BuildToolchain.compiler(), "go")
		if err != nil {
			base.Fatalf("%v", err)
		}
		fmt.Fprintf(h, "compile %s %q %q\n", id, forcedGccgoflags, p.Internal.Gccgoflags)
		fmt.Fprintf(h, "pkgpath %s\n", gccgoPkgpath(p))
		fmt.Fprintf(h, "ar %q\n", BuildToolchain.(gccgoToolchain).ar())
		if len(p.SFiles) > 0 {
			id, _ = b.gccToolID(BuildToolchain.compiler(), "assembler-with-cpp")
			// 忽略错误；不同的汇编程序版本
			// 无论如何都不可能有任何不同。
			fmt.Fprintf(h, "asm %q\n", id)
		}
	}

	// 输入文件。
	inputFiles := str.StringList(
		p.GoFiles,
		p.CgoFiles,
		p.CFiles,
		p.CXXFiles,
		p.FFiles,
		p.MFiles,
		p.HFiles,
		p.SFiles,
		p.SysoFiles,
		p.SwigFiles,
		p.SwigCXXFiles,
		p.EmbedFiles,
	)
	for _, file := range inputFiles {
		fmt.Fprintf(h, "file %s %s\n", file, b.fileHash(filepath.Join(p.Dir, file)))
	}
	for _, a1 := range a.Deps {
		p1 := a1.Package
		if p1 != nil {
			fmt.Fprintf(h, "import %s %s\n", p1.ImportPath, contentID(a1.buildID))
		}
	}

	return h.Sum()
}

// needCgoHdr报告此操作触发的操作是否能够访问cgo生成的头文件。
func (b *Builder) needCgoHdr(a *Action) bool {
	// 如果此生成触发了头安装，请运行cgo以获取头。
	if !b.IsCmdList && (a.Package.UsesCgo() || a.Package.UsesSwig()) && (cfg.BuildBuildmode == "c-archive" || cfg.BuildBuildmode == "c-shared") {
		for _, t1 := range a.triggers {
			if t1.Mode == "install header" {
				return true
			}
		}
		for _, t1 := range a.triggers {
			for _, t2 := range t1.triggers {
				if t2.Mode == "install header" {
					return true
				}
			}
		}
	}
	return false
}

// allowedVersion报告版本v是否是go 
// （我们可以编译的版本）的允许版本。
// v的形式已知为“1.23”。
func allowedVersion(v string) bool {
	// 特殊情况：无要求。
	if v == "" {
		return true
	}
	// 特例“1.0”表示“go1”，这是可以的。
	if v == "1.0" {
		return true
	}
	// 否则，请查看表单“go1.23”的发布标签，以查找匹配的发布标签。
	for _, tag := range cfg.BuildContext.ReleaseTags {
		if strings.HasPrefix(tag, "go") && tag[2:] == v {
			return true
		}
	}
	return false
}

const (
	needBuild uint32 = 1 << iota
	needCgoHdr
	needVet
	needCompiledGoFiles
	needStale
)

// build是构建单个包的操作。
// 请注意，对该逻辑的任何新影响也必须在上面的b.buildActionID中报告。
func (b *Builder) build(ctx context.Context, a *Action) (err error) {
	p := a.Package

	bit := func(x uint32, b bool) uint32 {
		if b {
			return x
		}
		return 0
	}

	cachedBuild := false
	need := bit(needBuild, !b.IsCmdList && a.needBuild || b.NeedExport) |
		bit(needCgoHdr, b.needCgoHdr(a)) |
		bit(needVet, a.needVet) |
		bit(needCompiledGoFiles, b.NeedCompiledGoFiles)

	if !p.BinaryOnly {
		if b.useCache(a, b.buildActionID(a), p.Target) {
			// 我们在缓存中找到了主要输出。
			// 如果我们不需要任何其他输出，我们可以停止。
			// 否则，我们需要将文件写入a.Objdir（needVet，needCgoHdr）。
			// 请记住，我们可能会将它们放在缓存中
			// 并在创建.Objdir后再次检查。
			cachedBuild = true
			a.output = []byte{} // 开始保存输出，以防我们错过任何缓存结果
			need &^= needBuild
			if b.NeedExport {
				p.Export = a.built
				p.BuildID = a.buildID
			}
			if need&needCompiledGoFiles != 0 {
				if err := b.loadCachedSrcFiles(a); err == nil {
					need &^= needCompiledGoFiles
				}
			}
		}

		// 源文件可能会被缓存，即使完整操作不是
		// （例如，go list-compiled-find）。
		if !cachedBuild && need&needCompiledGoFiles != 0 {
			if err := b.loadCachedSrcFiles(a); err == nil {
				need &^= needCompiledGoFiles
			}
		}

		if need == 0 {
			return nil
		}
		defer b.flushOutput(a)
	}

	defer func() {
		if err != nil && err != errPrintedOutput {
			err = fmt.Errorf("go build %s: %v", a.Package.ImportPath, err)
		}
		if err != nil && b.IsCmdList && b.NeedError && p.Error == nil {
			p.Error = &load.PackageError{Err: err}
		}
	}()
	if cfg.BuildN {
		// 在-n模式下，在包之间打印横幅。
		// 横幅是五行的，因此当对
		// 引导脚本的不同部分进行更改时，必须合并
		// 横幅为补丁提供一些
		// 以用于查找其上下文。
		b.Print("\n#\n# " + a.Package.ImportPath + "\n#\n\n")
	}

	if cfg.BuildV {
		b.Print(a.Package.ImportPath + "\n")
	}

	if a.Package.BinaryOnly {
		p.Stale = true
		p.StaleReason = "binary-only packages are no longer supported"
		if b.IsCmdList {
			return nil
		}
		return errors.New("binary-only packages are no longer supported")
	}

	if err := b.Mkdir(a.Objdir); err != nil {
		return err
	}
	objdir := a.Objdir

	// 加载缓存的cgo头，但前提是我们跳过主生成（cachedBuild==true）。
	if cachedBuild && need&needCgoHdr != 0 {
		if err := b.loadCachedCgoHdr(a); err == nil {
			need &^= needCgoHdr
		}
	}

	// 加载缓存的vet配置，但前提是我们只剩下这些了
	// （need==needVet，不只是测试一位）。
	// 如果我们无论如何都要进行完整构建，
	// 我们无论如何都要重新生成下面的文件。
	if need == needVet {
		if err := b.loadCachedVet(a); err == nil {
			need &^= needVet
		}
	}
	if need == 0 {
		return nil
	}

	if err := allowInstall(a); err != nil {
		return err
	}

	// 生成目标目录
	dir, _ := filepath.Split(a.Target)
	if dir != "" {
		if err := b.Mkdir(dir); err != nil {
			return err
		}
	}

	gofiles := str.StringList(a.Package.GoFiles)
	cgofiles := str.StringList(a.Package.CgoFiles)
	cfiles := str.StringList(a.Package.CFiles)
	sfiles := str.StringList(a.Package.SFiles)
	cxxfiles := str.StringList(a.Package.CXXFiles)
	var objects, cgoObjects, pcCFLAGS, pcLDFLAGS []string

	if a.Package.UsesCgo() || a.Package.UsesSwig() {
		if pcCFLAGS, pcLDFLAGS, err = b.getPkgConfigFlags(a.Package); err != nil {
			return
		}
	}

	// 计算覆盖。c/。抄送/。h/等，如果有任何重叠
	// 将所有这些文件的正确内容放入objdir中，以确保包含正确的标题。nonGoOverlay是
	// 从nongo文件指向objdir中复制的文件的覆盖。
	nonGoFileLists := [][]string{a.Package.CFiles, a.Package.SFiles, a.Package.CXXFiles, a.Package.HFiles, a.Package.FFiles}
OverlayLoop:
	for _, fs := range nonGoFileLists {
		for _, f := range fs {
			if _, ok := fsys.OverlayPath(mkAbs(p.Dir, f)); ok {
				a.nonGoOverlay = make(map[string]string)
				break OverlayLoop
			}
		}
	}
	if a.nonGoOverlay != nil {
		for _, fs := range nonGoFileLists {
			for i := range fs {
				from := mkAbs(p.Dir, fs[i])
				opath, _ := fsys.OverlayPath(from)
				dst := objdir + filepath.Base(fs[i])
				if err := b.copyFile(dst, opath, 0666, false); err != nil {
					return err
				}
				a.nonGoOverlay[from] = dst
			}
		}
	}

	// 在每个上运行SWIG。大口大口地喝。swigcxx文件。
	// 每次运行将生成两个文件，一个。去整理文件。c或。cxx文件。
	// 这个。go文件将使用导入“C”，由cgo处理。
	if a.Package.UsesSwig() {
		outGo, outC, outCXX, err := b.swig(a, a.Package, objdir, pcCFLAGS)
		if err != nil {
			return err
		}
		cgofiles = append(cgofiles, outGo...)
		cfiles = append(cfiles, outC...)
		cxxfiles = append(cxxfiles, outCXX...)
	}

	// 如果我们正在进行覆盖，请对数据进行预处理。go文件并将其放入工作目录
	if a.Package.Internal.CoverMode != "" {
		for i, file := range str.StringList(gofiles, cgofiles) {
			var sourceFile string
			var coverFile string
			var key string
			if strings.HasSuffix(file, ".cgo1.go") {
				// cgo文件具有绝对路径
				base := filepath.Base(file)
				sourceFile = file
				coverFile = objdir + base
				key = strings.TrimSuffix(base, ".cgo1.go") + ".go"
			} else {
				sourceFile = filepath.Join(a.Package.Dir, file)
				coverFile = objdir + file
				key = file
			}
			coverFile = strings.TrimSuffix(coverFile, ".go") + ".cover.go"
			cover := a.Package.Internal.CoverVars[key]
			if cover == nil || base.IsTestFile(file) {
				// 不包含此文件。
				continue
			}
			if err := b.cover(a, coverFile, sourceFile, cover.Var); err != nil {
				return err
			}
			if i < len(gofiles) {
				gofiles[i] = coverFile
			} else {
				cgofiles[i-len(gofiles)] = coverFile
			}
		}
	}

	// 运行cgo。ABCFDG 
	if a.Package.UsesCgo() || a.Package.UsesSwig() {
		// 有一个例外：runtime/cgo的工作是连接
		// cgo和非cgo世界，所以它必须在两者中都有文件。
		// 在这种情况下，gcc只获取gcc_*文件。
		var gccfiles []string
		gccfiles = append(gccfiles, cfiles...)
		cfiles = nil
		if a.Package.Standard && a.Package.ImportPath == "runtime/cgo" {
			filter := func(files, nongcc, gcc []string) ([]string, []string) {
				for _, f := range files {
					if strings.HasPrefix(f, "gcc_") {
						gcc = append(gcc, f)
					} else {
						nongcc = append(nongcc, f)
					}
				}
				return nongcc, gcc
			}
			sfiles, gccfiles = filter(sfiles, sfiles[:0], gccfiles)
		} else {
			for _, sfile := range sfiles {
				data, err := os.ReadFile(filepath.Join(a.Package.Dir, sfile))
				if err == nil {
					if bytes.HasPrefix(data, []byte("TEXT")) || bytes.Contains(data, []byte("\nTEXT")) ||
						bytes.HasPrefix(data, []byte("DATA")) || bytes.Contains(data, []byte("\nDATA")) ||
						bytes.HasPrefix(data, []byte("GLOBL")) || bytes.Contains(data, []byte("\nGLOBL")) {
						return fmt.Errorf("package using cgo has Go assembly file %s", sfile)
					}
				}
			}
			gccfiles = append(gccfiles, sfiles...)
			sfiles = nil
		}

		outGo, outObj, err := b.cgo(a, base.Tool("cgo"), objdir, pcCFLAGS, pcLDFLAGS, mkAbsFiles(a.Package.Dir, cgofiles), gccfiles, cxxfiles, a.Package.MFiles, a.Package.FFiles)

		// CxX文件中的文件现在已由b.cgo处理。
		cxxfiles = nil

		if err != nil {
			return err
		}
		if cfg.BuildToolchainName == "gccgo" {
			cgoObjects = append(cgoObjects, a.Objdir+"_cgo_flags")
		}
		cgoObjects = append(cgoObjects, outObj...)
		gofiles = append(gofiles, outGo...)

		switch cfg.BuildBuildmode {
		case "c-archive", "c-shared":
			b.cacheCgoHdr(a)
		}
	}

	var srcfiles []string // /。去不去。go 
	srcfiles = append(srcfiles, gofiles...)
	srcfiles = append(srcfiles, sfiles...)
	srcfiles = append(srcfiles, cfiles...)
	srcfiles = append(srcfiles, cxxfiles...)
	b.cacheSrcFiles(a, srcfiles)

	// 运行cgo生成cgo头。
	need &^= needCgoHdr

	// 自包装后，仅进行健康检查。负载也已经检查过了。
	if len(gofiles) == 0 {
		return &load.NoGoError{Package: a.Package}
	}

	// 如果需要，准备Go vet配置。
	if need&needVet != 0 {
		buildVetConfig(a, srcfiles)
		need &^= needVet
	}
	if need&needCompiledGoFiles != 0 {
		if err := b.loadCachedSrcFiles(a); err != nil {
			return fmt.Errorf("loading compiled Go files from cache: %w", err)
		}
		need &^= needCompiledGoFiles
	}
	if need == 0 {
		// 没什么可做的了。
		return nil
	}

	// 从组件收集符号ABI要求。
	symabis, err := BuildToolchain.symabis(b, a, sfiles)
	if err != nil {
		return err
	}

	// 准备Go导入配置。
	// 我们以一条注释开始，这样它就不能是空的，所以icfg。下面的字节（）永远不会为零。
	// 无论如何它都不应该是空的，但过去有一些错误导致
	// 在空配置中，然后不幸地变成了“没有传递给编译器的配置”，
	// 编译器又回到了pkg本身，而pkg本身大部分是有效的，
	// 除非它没有。
	var icfg bytes.Buffer
	fmt.Fprintf(&icfg, "# import config\n")
	for i, raw := range a.Package.Internal.RawImports {
		final := a.Package.Imports[i]
		if final != raw {
			fmt.Fprintf(&icfg, "importmap %s=%s\n", raw, final)
		}
	}
	for _, a1 := range a.Deps {
		p1 := a1.Package
		if p1 == nil || p1.ImportPath == "" || a1.built == "" {
			continue
		}
		fmt.Fprintf(&icfg, "packagefile %s=%s\n", p1.ImportPath, a1.built)
	}

	// 如果需要，准备Go embed config。
	// 与导入配置不同，嵌入配置可以为空。
	var embedcfg []byte
	if len(p.Internal.Embed) > 0 {
		var embed struct {
			Patterns map[string][]string
			Files    map[string]string
		}
		embed.Patterns = p.Internal.Embed
		embed.Files = make(map[string]string)
		for _, file := range p.EmbedFiles {
			embed.Files[file] = filepath.Join(p.Dir, file)
		}
		js, err := json.MarshalIndent(&embed, "", "\t")
		if err != nil {
			return fmt.Errorf("marshal embedcfg: %v", err)
		}
		embedcfg = js
	}

	if p.Internal.BuildInfo != "" && cfg.ModulesEnabled {
		prog := modload.ModInfoProg(p.Internal.BuildInfo, cfg.BuildToolchainName == "gccgo")
		if len(prog) > 0 {
			if err := b.writeFile(objdir+"_gomod_.go", prog); err != nil {
				return err
			}
			gofiles = append(gofiles, objdir+"_gomod_.go")
		}
	}

	// 编译开始。
	objpkg := objdir + "_pkg_.a"
	ofile, out, err := BuildToolchain.gc(b, a, objpkg, icfg.Bytes(), embedcfg, symabis, len(sfiles) > 0, gofiles)
	if len(out) > 0 {
		output := b.processOutput(out)
		if p.Module != nil && !allowedVersion(p.Module.GoVersion) {
			output += "note: module requires Go " + p.Module.GoVersion + "\n"
		}
		b.showOutput(a, a.Package.Dir, a.Package.Desc(), output)
		if err != nil {
			return errPrintedOutput
		}
	}
	if err != nil {
		if p.Module != nil && !allowedVersion(p.Module.GoVersion) {
			b.showOutput(a, a.Package.Dir, a.Package.Desc(), "note: module requires Go "+p.Module.GoVersion+"\n")
		}
		return err
	}
	if ofile != objpkg {
		objects = append(objects, ofile)
	}

	// 复制。以goos或goarch或goos_goarch 
	// 使用GOOS和GOARCH命名。
	// 例如，defs_linux_amd64。h变成defs_GOOS_GOARCH。h、 
	_goos_goarch := "_" + cfg.Goos + "_" + cfg.Goarch
	_goos := "_" + cfg.Goos
	_goarch := "_" + cfg.Goarch
	for _, file := range a.Package.HFiles {
		name, ext := fileExtSplit(file)
		switch {
		case strings.HasSuffix(name, _goos_goarch):
			targ := file[:len(name)-len(_goos_goarch)] + "_GOOS_GOARCH." + ext
			if err := b.copyFile(objdir+targ, filepath.Join(a.Package.Dir, file), 0666, true); err != nil {
				return err
			}
		case strings.HasSuffix(name, _goarch):
			targ := file[:len(name)-len(_goarch)] + "_GOARCH." + ext
			if err := b.copyFile(objdir+targ, filepath.Join(a.Package.Dir, file), 0666, true); err != nil {
				return err
			}
		case strings.HasSuffix(name, _goos):
			targ := file[:len(name)-len(_goos)] + "_GOOS." + ext
			if err := b.copyFile(objdir+targ, filepath.Join(a.Package.Dir, file), 0666, true); err != nil {
				return err
			}
		}
	}

	for _, file := range cfiles {
		out := file[:len(file)-len(".c")] + ".o"
		if err := BuildToolchain.cc(b, a, objdir+out, file); err != nil {
			return err
		}
		objects = append(objects, out)
	}

	// 组装。这是我的文件。
	if len(sfiles) > 0 {
		ofiles, err := BuildToolchain.asm(b, a, sfiles)
		if err != nil {
			return err
		}
		objects = append(objects, ofiles...)
	}

	// 对于ELF系统上的gccgo，我们将构建ID作为汇编文件写入。
	// 这让我们可以设置SHF_排除标志。
	// 这是由cmd/internal/buildid/buildid中的readGccgoArchive读取的。去
	if a.buildID != "" && cfg.BuildToolchainName == "gccgo" {
		switch cfg.Goos {
		case "aix", "android", "dragonfly", "freebsd", "illumos", "linux", "netbsd", "openbsd", "solaris":
			asmfile, err := b.gccgoBuildIDFile(a)
			if err != nil {
				return err
			}
			ofiles, err := BuildToolchain.asm(b, a, []string{asmfile})
			if err != nil {
				return err
			}
			objects = append(objects, ofiles...)
		}
	}

	// 注意（rsc）：在Windows上，将
	// gcc编译对象（cgoObjects）列在归档文件中普通
	// 对象之后是至关重要的。我不知道这是为什么。
	// https:
	objects = append(objects, cgoObjects...)

	// 添加系统对象文件。
	for _, syso := range a.Package.SysoFiles {
		objects = append(objects, filepath.Join(a.Package.Dir, syso))
	}

	// 打包到objdir目录下的存档中。
	// 如果Go编译器编写了一个归档文件，我们只需要将非Go源的目标文件添加到归档文件中。
	// 如果Go编译器编写了一个归档文件，并且包完全是
	// Go sources，那么根本没有要执行的包。
	if len(objects) > 0 {
		if err := BuildToolchain.pack(b, a, objpkg, objects); err != nil {
			return err
		}
	}

	if err := b.updateBuildID(a, objpkg, true); err != nil {
		return err
	}

	a.built = objpkg
	return nil
}

func (b *Builder) cacheObjdirFile(a *Action, c *cache.Cache, name string) error {
	f, err := os.Open(a.Objdir + name)
	if err != nil {
		return err
	}
	defer f.Close()
	_, _, err = c.Put(cache.Subkey(a.actionID, name), f)
	return err
}

func (b *Builder) findCachedObjdirFile(a *Action, c *cache.Cache, name string) (string, error) {
	file, _, err := c.GetFile(cache.Subkey(a.actionID, name))
	if err != nil {
		return "", fmt.Errorf("loading cached file %s: %w", name, err)
	}
	return file, nil
}

func (b *Builder) loadCachedObjdirFile(a *Action, c *cache.Cache, name string) error {
	cached, err := b.findCachedObjdirFile(a, c, name)
	if err != nil {
		return err
	}
	return b.copyFile(a.Objdir+name, cached, 0666, true)
}

func (b *Builder) cacheCgoHdr(a *Action) {
	c := cache.Default()
	b.cacheObjdirFile(a, c, "_cgo_install.h")
}

func (b *Builder) loadCachedCgoHdr(a *Action) error {
	c := cache.Default()
	return b.loadCachedObjdirFile(a, c, "_cgo_install.h")
}

func (b *Builder) cacheSrcFiles(a *Action, srcfiles []string) {
	c := cache.Default()
	var buf bytes.Buffer
	for _, file := range srcfiles {
		if !strings.HasPrefix(file, a.Objdir) {
			// 未生成
			buf.WriteString("./")
			buf.WriteString(file)
			buf.WriteString("\n")
			continue
		}
		name := file[len(a.Objdir):]
		buf.WriteString(name)
		buf.WriteString("\n")
		if err := b.cacheObjdirFile(a, c, name); err != nil {
			return
		}
	}
	c.PutBytes(cache.Subkey(a.actionID, "srcfiles"), buf.Bytes())
}

func (b *Builder) loadCachedVet(a *Action) error {
	c := cache.Default()
	list, _, err := c.GetBytes(cache.Subkey(a.actionID, "srcfiles"))
	if err != nil {
		return fmt.Errorf("reading srcfiles list: %w", err)
	}
	var srcfiles []string
	for _, name := range strings.Split(string(list), "\n") {
		if name == "" { // 列表结束
			continue
		}
		if strings.HasPrefix(name, "./") {
			srcfiles = append(srcfiles, name[2:])
			continue
		}
		if err := b.loadCachedObjdirFile(a, c, name); err != nil {
			return err
		}
		srcfiles = append(srcfiles, a.Objdir+name)
	}
	buildVetConfig(a, srcfiles)
	return nil
}

func (b *Builder) loadCachedSrcFiles(a *Action) error {
	c := cache.Default()
	list, _, err := c.GetBytes(cache.Subkey(a.actionID, "srcfiles"))
	if err != nil {
		return fmt.Errorf("reading srcfiles list: %w", err)
	}
	var files []string
	for _, name := range strings.Split(string(list), "\n") {
		if name == "" { // 列表结束
			continue
		}
		if strings.HasPrefix(name, "./") {
			files = append(files, name[len("./"):])
			continue
		}
		file, err := b.findCachedObjdirFile(a, c, name)
		if err != nil {
			return fmt.Errorf("finding %s: %w", name, err)
		}
		files = append(files, file)
	}
	a.Package.CompiledGoFiles = files
	return nil
}

// vetConfig是传递给vet的描述单个包的配置。
type vetConfig struct {
	ID           string   // 包ID（例如：“fmt[fmt.test]”）
	Compiler     string   // 编译器名称（gc，gccgo）
	Dir          string   // 包含包的目录
	ImportPath   string   // 规范导入路径（“包路径”）
	GoFiles      []string // 包源文件的绝对路径
	NonGoFiles   []string // 包非Go文件的绝对路径
	IgnoredFiles []string // 忽略的绝对路径源文件

	ImportMap   map[string]string // 将源代码中的导入路径映射到包路径
	PackageFile map[string]string // 将包路径映射到。带有导出数据的文件
	Standard    map[string]bool   // 将包路径映射到是否在标准库
	PackageVetx map[string]string // 将包路径映射到早期vet运行的vetx数据
	VetxOnly    bool              // 仅计算vetx数据；不要报告检测到的问题
	VetxOutput  string            // 将vetx数据写入此输出文件

	SucceedOnTypecheckFailure bool // 糟糕的黑客行为；参见#18395及以下
}

func buildVetConfig(a *Action, srcfiles []string) {
	// 根据分类文件。去接分机。
	// srcfiles不包括原始cgo文件。
	var gofiles, nongofiles []string
	for _, name := range srcfiles {
		if strings.HasSuffix(name, ".go") {
			gofiles = append(gofiles, name)
		} else {
			nongofiles = append(nongofiles, name)
		}
	}

	ignored := str.StringList(a.Package.IgnoredGoFiles, a.Package.IgnoredOtherFiles)

	// 将绝对路径列表传递给vet，
	// 以便vet的错误消息将使用绝对路径，
	// 以便我们可以相对于调用go命令的目录
	// 重新格式化它们。
	vcfg := &vetConfig{
		ID:           a.Package.ImportPath,
		Compiler:     cfg.BuildToolchainName,
		Dir:          a.Package.Dir,
		GoFiles:      mkAbsFiles(a.Package.Dir, gofiles),
		NonGoFiles:   mkAbsFiles(a.Package.Dir, nongofiles),
		IgnoredFiles: mkAbsFiles(a.Package.Dir, ignored),
		ImportPath:   a.Package.ImportPath,
		ImportMap:    make(map[string]string),
		PackageFile:  make(map[string]string),
		Standard:     make(map[string]bool),
	}
	a.vetCfg = vcfg
	for i, raw := range a.Package.Internal.RawImports {
		final := a.Package.Imports[i]
		vcfg.ImportMap[raw] = final
	}

	// 在vet config 
	// 中计算映射导入的列表，以便我们可以在下面添加任何缺少的映射。
	vcfgMapped := make(map[string]bool)
	for _, p := range vcfg.ImportMap {
		vcfgMapped[p] = true
	}

	for _, a1 := range a.Deps {
		p1 := a1.Package
		if p1 == nil || p1.ImportPath == "" {
			continue
		}
		// 如果需要，添加导入映射
		// （对于仅在生成的代码中出现的“runtime/cgo”等导入）。
		if !vcfgMapped[p1.ImportPath] {
			vcfg.ImportMap[p1.ImportPath] = p1.ImportPath
		}
		if a1.built != "" {
			vcfg.PackageFile[p1.ImportPath] = a1.built
		}
		if p1.Standard {
			vcfg.Standard[p1.ImportPath] = true
		}
	}
}

// VetTool是指向备用vet tool二进制文件的路径。
// 在执行任何vet操作之前，调用方需要设置它（如果需要）。
var VetTool string

// VetFlags是传递给vet的默认标志。
// 在执行任何vet操作之前，调用方需要设置它们。
var VetFlags []string

// vet EXPLICIT记录是否在命令行上显式设置了vet标志。
var VetExplicit bool

func (b *Builder) vet(ctx context.Context, a *Action) error {
	// a.Deps[0]是正在审查的包的版本。
	// a.Deps[1]是“fmt”包的构建。

	a.Failed = false // 依赖项检查可能失败了，但我们仍然可以成功

	if a.Deps[0].Failed {
		// 包的构建失败了。跳过兽医检查。
		// Vet可以返回非类型检查错误的导出数据，
		// 但是我们忽略它，因为包无法编译。
		return nil
	}

	vcfg := a.Deps[0].vetCfg
	if vcfg == nil {
		// 只有在生成失败时，才应缺少Vet config。
		return fmt.Errorf("vet config not found")
	}

	vcfg.VetxOnly = a.VetxOnly
	vcfg.VetxOutput = a.Objdir + "vet.out"
	vcfg.PackageVetx = make(map[string]string)

	h := cache.NewHash("vet " + a.Package.ImportPath)
	fmt.Fprintf(h, "vet %q\n", b.toolID("vet"))

	vetFlags := VetFlags

	// 在GOROOT中，我们启用“go test”期间的所有vet测试，而不仅仅是高置信度子集。这为我们提供了额外的
	// 检查标准库（以一定的合规性成本）
	// 并帮助我们获得有关检查
	// 工作情况的经验，以帮助我们决定默认情况下应打开哪个库。
	// 命令行仍然获胜。
	// 
	// 请注意，即使在以
	// 身份运行vet时，此标志更改也适用。
	// （否则，我们必须引入一个单独的
	// 空间“vet fmt作为std顶级兽医的依赖”
	// “将fmt视为非性病顶级兽医的依赖项”。）
	// 只要在
	// 依赖关系树下较远的包打开*more*分析，就可以了，如下所示。
	// （unsafeptr检查不会写入任何事实供
	// 以后的vet运行使用，也不会无法访问。）
	if a.Package.Goroot && !VetExplicit && VetTool == "" {
		// 关闭-未安全检查。
		// 有太多不安全的东西。指针代码
		// vet在低级包中不喜欢的代码
		// 。
		// 请注意$GOROOT/src/buildall。bash 
		// 对misc compile trybots 
		// 执行相同的操作，如果这些标志是
		// 在此处更改，则应进行更新。
		vetFlags = []string{"-unsafeptr=false"}

		// 同时关闭go测试期间无法访问的检查。
		// 在测试过程中，经常会做出更改
		// 例如硬编码的强制返回或使
		// 代码无法访问的恐慌。坚持文件
		// 在“go test”期间没有任何无法访问的代码是不合理的。
		// （buildall.bash在整个树扫描中仍然使用-unreachable enabled 
		// ）
		if cfg.CmdName == "test" {
			vetFlags = append(vetFlags, "-unreachable=false")
		}
	}

	// 注意：我们可以决定vet应该计算
	// 所有分析的导出数据，在这种情况下，我们不需要在这里包含标志。
	// 但这意味着，如果一项分析导致像
	// 意外崩溃这样的问题，就无法关闭它。
	// 让标志也禁用导出分析似乎更好。
	fmt.Fprintf(h, "vetflags %q\n", vetFlags)

	fmt.Fprintf(h, "pkg %q\n", a.Deps[0].actionID)
	for _, a1 := range a.Deps {
		if a1.Mode == "vet" && a1.built != "" {
			fmt.Fprintf(h, "vetout %q %s\n", a1.Package.ImportPath, b.fileHash(a1.built))
			vcfg.PackageVetx[a1.Package.ImportPath] = a1.built
		}
	}
	key := cache.ActionID(h.Sum())

	if vcfg.VetxOnly && !cfg.BuildA {
		c := cache.Default()
		if file, _, err := c.GetFile(key); err == nil {
			a.built = file
			return nil
		}
	}

	js, err := json.MarshalIndent(vcfg, "", "\t")
	if err != nil {
		return fmt.Errorf("internal error marshaling vet config: %v", err)
	}
	js = append(js, '\n')
	if err := b.writeFile(a.Objdir+"vet.cfg", js); err != nil {
		return err
	}

	// TODO（rsc）：为什么我们要通过$GCCGO去看兽医？
	env := b.cCompilerEnv()
	if cfg.BuildToolchainName == "gccgo" {
		env = append(env, "GCCGO="+BuildToolchain.compiler())
	}

	p := a.Package
	tool := VetTool
	if tool == "" {
		tool = base.Tool("vet")
	}
	runErr := b.run(a, p.Dir, p.ImportPath, env, cfg.BuildToolexec, tool, vetFlags, a.Objdir+"vet.cfg")

	// 如果兽医编写了导出数据，请将其保存以供将来的兽医输入。
	if f, err := os.Open(vcfg.VetxOutput); err == nil {
		a.built = vcfg.VetxOutput
		cache.Default().Put(key, f)
		f.Close()
	}

	return runErr
}

// linkActionID计算链接操作的操作ID。
func (b *Builder) linkActionID(a *Action) cache.ActionID {
	p := a.Package
	h := cache.NewHash("link " + p.ImportPath)

	// 工具链独立配置。
	fmt.Fprintf(h, "link\n")
	fmt.Fprintf(h, "buildmode %s goos %s goarch %s\n", cfg.BuildBuildmode, cfg.Goos, cfg.Goarch)
	fmt.Fprintf(h, "import %q\n", p.ImportPath)
	fmt.Fprintf(h, "omitdebug %v standard %v local %v prefix %q\n", p.Internal.OmitDebug, p.Standard, p.Internal.Local, p.Internal.LocalPrefix)
	if cfg.BuildTrimpath {
		fmt.Fprintln(h, "trimpath")
	}

	// 依赖于工具链的配置，与b.linkSharedActionID共享。
	b.printLinkerConfig(h, p)

	// 输入文件。
	for _, a1 := range a.Deps {
		p1 := a1.Package
		if p1 != nil {
			if a1.built != "" || a1.buildID != "" {
				buildID := a1.buildID
				if buildID == "" {
					buildID = b.buildID(a1.built)
				}
				fmt.Fprintf(h, "packagefile %s=%s\n", p1.ImportPath, contentID(buildID))
			}
			// 因为我们将包main的完整操作ID放入二进制的构建ID中，所以
			// 我们还必须将完整操作ID放入二进制的操作ID哈希中。
			if p1.Name == "main" {
				fmt.Fprintf(h, "packagemain %s\n", a1.buildID)
			}
			if p1.Shlib != "" {
				fmt.Fprintf(h, "packageshlib %s=%s\n", p1.ImportPath, contentID(b.buildID(p1.Shlib)))
			}
		}
	}

	return h.Sum()
}

// printLinkerConfig将链接器配置打印到哈希h中，
// 作为与链接器相关的操作ID计算的一部分。
func (b *Builder) printLinkerConfig(h io.Writer, p *load.Package) {
	switch cfg.BuildToolchainName {
	default:
		base.Fatalf("linkActionID: unknown toolchain %q", cfg.BuildToolchainName)

	case "gc":
		fmt.Fprintf(h, "link %s %q %s\n", b.toolID("link"), forcedLdflags, ldBuildmode)
		if p != nil {
			fmt.Fprintf(h, "linkflags %q\n", p.Internal.Ldflags)
		}

		// GOARM、GOMIPS等。
		key, val := cfg.GetArchEnv()
		fmt.Fprintf(h, "%s=%s\n", key, val)

		if goexperiment := buildcfg.GOEXPERIMENT(); goexperiment != "" {
			fmt.Fprintf(h, "GOEXPERIMENT=%q\n", goexperiment)
		}

		// 链接器只在未指定-trimpath的情况下（请参见gc中的ld（））。开始）。
		gorootFinal := cfg.GOROOT_FINAL
		if cfg.BuildTrimpath {
			gorootFinal = trimPathGoRootFinal
		}
		fmt.Fprintf(h, "GOROOT=%s\n", gorootFinal)

		// GO_EXTLINK_ENABLED控制是否使用外部链接器。
		fmt.Fprintf(h, "GO_EXTLINK_ENABLED=%s\n", cfg.Getenv("GO_EXTLINK_ENABLED"))

		// TODO（rsc）：是否需要包括cgo设置和标志？
		// 或外部链接器设置和标志？

	case "gccgo":
		id, err := b.gccToolID(BuildToolchain.linker(), "go")
		if err != nil {
			base.Fatalf("%v", err)
		}
		fmt.Fprintf(h, "link %s %s\n", id, ldBuildmode)
		// TODO（iant）：这里可能应该包括cgo标志。
	}
}

// 链接是链接单个命令的操作。
// 请注意，对该逻辑的任何新影响也必须在上面的b.linkActionID中报告。
func (b *Builder) link(ctx context.Context, a *Action) (err error) {
	if b.useCache(a, b.linkActionID(a), a.Package.Target) || b.IsCmdList {
		return nil
	}
	defer b.flushOutput(a)

	if err := b.Mkdir(a.Objdir); err != nil {
		return err
	}

	importcfg := a.Objdir + "importcfg.link"
	if err := b.writeLinkImportcfg(a, importcfg); err != nil {
		return err
	}

	if err := allowInstall(a); err != nil {
		return err
	}

	// 使目标目录
	dir, _ := filepath.Split(a.Target)
	if dir != "" {
		if err := b.Mkdir(dir); err != nil {
			return err
		}
	}

	if err := BuildToolchain.ld(b, a, a.Target, importcfg, a.Deps[0].built); err != nil {
		return err
	}

	// 使用最终生成ID更新二进制文件。
	// 但是如果设置了OmitDebug，不要重写二进制文件，因为我们在要运行的二进制文件上设置了OmitDebug 
	// 然后删除。
	// 在这样一个二进制文件上工作是没有意义的。
	// 更糟糕的是，在这里打开二进制文件进行写操作会使它无法安全地执行fork+exec，因为在现代Unix系统上ETXTBSY和线程之间存在根本的不兼容。
	// 见golang。org/issue/22220。
	// 我们仍然调用updateBuildID来更新a.buildID，这对于测试结果缓存很重要，但是传递rewrite=false（final arg）
	// 意味着我们实际上不重写二进制文件，也不将
	// 结果存储到缓存中。这可能是一个净胜利：减少了在大型二进制文件上浪费的缓存空间，这是我们不太可能再次需要的。（另一方面，它确实会降低重复go测试的速度。）
	// 这也会降低重复go-run的速度，这本身就是一个胜利：
	// 我们不希望人们将go-run视为一个脚本环境。
	if err := b.updateBuildID(a, a.Target, !a.Package.Internal.OmitDebug); err != nil {
		return err
	}

	a.built = a.Target
	return nil
}

func (b *Builder) writeLinkImportcfg(a *Action, file string) error {
	// 准备Go导入cfg。
	var icfg bytes.Buffer
	for _, a1 := range a.Deps {
		p1 := a1.Package
		if p1 == nil {
			continue
		}
		fmt.Fprintf(&icfg, "packagefile %s=%s\n", p1.ImportPath, a1.built)
		if p1.Shlib != "" {
			fmt.Fprintf(&icfg, "packageshlib %s=%s\n", p1.ImportPath, p1.Shlib)
		}
	}
	fmt.Fprintf(&icfg, "modinfo %q\n", modload.ModInfoData(a.Package.Internal.BuildInfo))
	return b.writeFile(file, icfg.Bytes())
}

// PkgconfigCmd返回一个pkg配置二进制名称
// defaultPkgConfig在zdefaultcc中定义。go，由cmd/dist.
func (b *Builder) PkgconfigCmd() string {
	return envList("PKG_CONFIG", cfg.DefaultPkgConfig)[0]
}

// splitPkgConfigOutput编写，将pkg config输出解析为
// flags的一部分。这实现了pkgconf/libpkgconf/argvsplit中的算法。c、 
func splitPkgConfigOutput(out []byte) ([]string, error) {
	if len(out) == 0 {
		return nil, nil
	}
	var flags []string
	flag := make([]byte, 0, len(out))
	escaped := false
	quote := byte(0)

	for _, c := range out {
		if escaped {
			if quote != 0 {
				switch c {
				case '$', '`', '"', '\\':
				default:
					flag = append(flag, '\\')
				}
				flag = append(flag, c)
			} else {
				flag = append(flag, c)
			}
			escaped = false
		} else if quote != 0 {
			if c == quote {
				quote = 0
			} else {
				switch c {
				case '\\':
					escaped = true
				default:
					flag = append(flag, c)
				}
			}
		} else if strings.IndexByte(" \t\n\v\f\r", c) < 0 {
			switch c {
			case '\\':
				escaped = true
			case '\'', '"':
				quote = c
			default:
				flag = append(flag, c)
			}
		} else if len(flag) != 0 {
			flags = append(flags, string(flag))
			flag = flag[:0]
		}
	}
	if escaped {
		return nil, errors.New("broken character escaping in pkgconf output ")
	}
	if quote != 0 {
		return nil, errors.New("unterminated quoted string in pkgconf output ")
	} else if len(flag) != 0 {
		flags = append(flags, string(flag))
	}

	return flags, nil
}

// 如果需要，调用pkg config并返回构建包所需的cflags/ldflags。
func (b *Builder) getPkgConfigFlags(p *load.Package) (cflags, ldflags []string, err error) {
	if pcargs := p.CgoPkgConfig; len(pcargs) > 0 {
		// pkg config允许参数出现在命令行中的任何位置。把他们都移到前面，在--。
		var pcflags []string
		var pkgs []string
		for _, pcarg := range pcargs {
			if pcarg == "--" {
				// 我们将添加我们自己的“-”参数。
			} else if strings.HasPrefix(pcarg, "--") {
				pcflags = append(pcflags, pcarg)
			} else {
				pkgs = append(pkgs, pcarg)
			}
		}
		for _, pkg := range pkgs {
			if !load.SafeArg(pkg) {
				return nil, nil, fmt.Errorf("invalid pkg-config package name: %s", pkg)
			}
		}
		var out []byte
		out, err = b.runOut(nil, p.Dir, nil, b.PkgconfigCmd(), "--cflags", pcflags, "--", pkgs)
		if err != nil {
			b.showOutput(nil, p.Dir, b.PkgconfigCmd()+" --cflags "+strings.Join(pcflags, " ")+" -- "+strings.Join(pkgs, " "), string(out))
			b.Print(err.Error() + "\n")
			return nil, nil, errPrintedOutput
		}
		if len(out) > 0 {
			cflags, err = splitPkgConfigOutput(out)
			if err != nil {
				return nil, nil, err
			}
			if err := checkCompilerFlags("CFLAGS", "pkg-config --cflags", cflags); err != nil {
				return nil, nil, err
			}
		}
		out, err = b.runOut(nil, p.Dir, nil, b.PkgconfigCmd(), "--libs", pcflags, "--", pkgs)
		if err != nil {
			b.showOutput(nil, p.Dir, b.PkgconfigCmd()+" --libs "+strings.Join(pcflags, " ")+" -- "+strings.Join(pkgs, " "), string(out))
			b.Print(err.Error() + "\n")
			return nil, nil, errPrintedOutput
		}
		if len(out) > 0 {
			// 注意：我们这里不尝试解析引号和unescape。pkg config 
			// 通常在shell backticks中使用，它按字面意思处理引号。
			ldflags = strings.Fields(string(out))
			if err := checkLinkerFlags("LDFLAGS", "pkg-config --libs", ldflags); err != nil {
				return nil, nil, err
			}
		}
	}

	return
}

func (b *Builder) installShlibname(ctx context.Context, a *Action) error {
	if err := allowInstall(a); err != nil {
		return err
	}

	// TODO:BuildN 
	a1 := a.Deps[0]
	err := os.WriteFile(a.Target, []byte(filepath.Base(a1.Target)+"\n"), 0666)
	if err != nil {
		return err
	}
	if cfg.BuildX {
		b.Showcmd("", "echo '%s' > %s # internal", filepath.Base(a1.Target), a.Target)
	}
	return nil
}

func (b *Builder) linkSharedActionID(a *Action) cache.ActionID {
	h := cache.NewHash("linkShared")

	// 工具链独立配置。
	fmt.Fprintf(h, "linkShared\n")
	fmt.Fprintf(h, "goos %s goarch %s\n", cfg.Goos, cfg.Goarch)

	// 依赖于工具链的配置，与b.linkActionID共享。
	b.printLinkerConfig(h, nil)

	// 输入文件。
	for _, a1 := range a.Deps {
		p1 := a1.Package
		if a1.built == "" {
			continue
		}
		if p1 != nil {
			fmt.Fprintf(h, "packagefile %s=%s\n", p1.ImportPath, contentID(b.buildID(a1.built)))
			if p1.Shlib != "" {
				fmt.Fprintf(h, "packageshlib %s=%s\n", p1.ImportPath, contentID(b.buildID(p1.Shlib)))
			}
		}
	}
	// 命令行上命名的文件是特殊的。
	for _, a1 := range a.Deps[0].Deps {
		p1 := a1.Package
		fmt.Fprintf(h, "top %s=%s\n", p1.ImportPath, contentID(b.buildID(a1.built)))
	}

	return h.Sum()
}

func (b *Builder) linkShared(ctx context.Context, a *Action) (err error) {
	if b.useCache(a, b.linkSharedActionID(a), a.Target) || b.IsCmdList {
		return nil
	}
	defer b.flushOutput(a)

	if err := allowInstall(a); err != nil {
		return err
	}

	if err := b.Mkdir(a.Objdir); err != nil {
		return err
	}

	importcfg := a.Objdir + "importcfg.link"
	if err := b.writeLinkImportcfg(a, importcfg); err != nil {
		return err
	}

	// TODO（rsc）：这里缺少updateBuildID，
	// 但我们必须决定在这些文件中存储构建ID的位置。
	a.built = a.Target
	return BuildToolchain.ldShared(b, a, a.Deps[0].Deps, a.Target, importcfg, a.Deps)
}

// BuildInstallFunc是安装单个软件包或可执行文件的操作。
func BuildInstallFunc(b *Builder, ctx context.Context, a *Action) (err error) {
	defer func() {
		if err != nil && err != errPrintedOutput {
			// a.Package==nil可用于安装libmangledname的go安装-buildmode=shared 
			// 操作。所以，它对应于
			// 一个包列表，而不仅仅是一个。
			sep, path := "", ""
			if a.Package != nil {
				sep, path = " ", a.Package.ImportPath
			}
			err = fmt.Errorf("go %s%s%s: %v", cfg.CmdName, sep, path, err)
		}
	}()

	a1 := a.Deps[0]
	a.buildID = a1.buildID
	if a.json != nil {
		a.json.BuildID = a.buildID
	}

	// 如果我们使用最终安装目标作为我们构建的东西的最新
	// 缓存副本，那么就没有必要将其复制到自身中（而且可能会失败）。
	// 在本例中为a1。Build==a。目标是a1。builded==p.Target，
	// 所以builded目标不在a1中。b.cleanup（a1）删除的Objdir树。
	if a1.built == a.Target {
		a.built = a.Target
		if !a.buggyInstall {
			b.cleanup(a1)
		}
		// 我们是否足够聪明来避免完全重建
		// 取决于过时和重建算法
		// 以及Go build缓存的潜在状态。
		// 我们真的不希望用户能够推断（或更糟糕的开始取决于）
		// 在
		// “go install”期间修改时间是否发生变化，因此尽最大努力更新文件时间，使其看起来像是我们重写了a.Target，即使我们没有。更新mtime 
		// 也可能有助于依赖我们的
		// 以前更频繁的mtime更新的其他基于mtime的系统。
		// 这仍然不是完美的-我们忽略错误结果，如果文件由于某种原因无法写入，那么假装已经编写了它也是令人困惑的-但这可能比不进行mtime更新要好。
		// 
		// 但在特殊情况下，不要这样做，即使用-linkshared隐式安装其所有依赖库来构建可执行文件
		// 。
		// 我们希望尽可能地隐藏那些可怕的细节，所以不要通过触摸MTIME来宣传它（通常库是最新的）。
		if !a.buggyInstall && !b.IsCmdList {
			if cfg.BuildN {
				b.Showcmd("", "touch %s", a.Target)
			} else if err := allowInstall(a); err == nil {
				now := time.Now()
				os.Chtimes(a.Target, now, now)
			}
		}
		return nil
	}

	// 如果我们正在为go list-export构建，
	// 永远不要安装任何东西；保留缓存引用即可。
	if b.IsCmdList {
		a.built = a1.built
		return nil
	}
	if err := allowInstall(a); err != nil {
		return err
	}

	if err := b.Mkdir(a.Objdir); err != nil {
		return err
	}

	perm := fs.FileMode(0666)
	if a1.Mode == "link" {
		switch cfg.BuildBuildmode {
		case "c-archive", "c-shared", "plugin":
		default:
			perm = 0777
		}
	}

	// make target directory 
	dir, _ := filepath.Split(a.Target)
	if dir != "" {
		if err := b.Mkdir(dir); err != nil {
			return err
		}
	}

	if !a.buggyInstall {
		defer b.cleanup(a1)
	}

	return b.moveOrCopyFile(a.Target, a1.built, perm, false)
}

// allowistall返回一个非零错误，如果对go命令的调用是
// 允许安装一个.target。
// 
// （在自己的测试下运行的cmd/go版本禁止将
// 安装到其原始GOROOT。）
var allowInstall = func(*Action) error { return nil }

// cleanup删除a的object dir，以在大型构建中减少磁盘上的
// 垃圾量。在操作系统
// 上，使用积极的缓冲，像
// 那样进行增量清理，这样可以防止中间对象撞击磁盘。
func (b *Builder) cleanup(a *Action) {
	if !cfg.BuildWork {
		if cfg.BuildX {
			// 如果我们从未创建过该目录，请不要说我们正在删除该目录。
			if _, err := os.Stat(a.Objdir); err == nil || cfg.BuildN {
				b.Showcmd("", "rm -r %s", a.Objdir)
			}
		}
		os.RemoveAll(a.Objdir)
	}
}

// moveOrCopyFile类似于“mv src dst”或“cp src dst”。
func (b *Builder) moveOrCopyFile(dst, src string, perm fs.FileMode, force bool) error {
	if cfg.BuildN {
		b.Showcmd("", "mv %s %s", src, dst)
		return nil
	}

	// 如果我们可以更新模式并重命名为dst，那么就这样做。
	// 否则返回标准副本。

	// 如果源代码在生成缓存中，我们需要复制它。
	if strings.HasPrefix(src, cache.DefaultDir()) {
		return b.copyFile(dst, src, perm, force)
	}

	// 在Windows上，始终复制文件，以便我们尊重父文件夹的NTFS 
	// 权限。https:
	// 这里重要的不是cfg。Goos（我们正在为其构建
	// 的系统）但是运行时。GOOS（我们正在构建的系统）。
	if runtime.GOOS == "windows" {
		return b.copyFile(dst, src, perm, force)
	}

	// 如果目标目录设置了组粘性位，
	// 我们必须复制文件以保留正确的权限。
	// https:
	if fi, err := os.Stat(filepath.Dir(dst)); err == nil {
		if fi.IsDir() && (fi.Mode()&fs.ModeSetgid) != 0 {
			return b.copyFile(dst, src, perm, force)
		}
	}

	// perm参数是根据umask进行调整的，但是我们不知道umask是什么。
	// 创建一个虚拟文件来查找。
	// 这避免了生成标记，即使在Plan 9 
	// 这样的系统中，文件掩码计算也会包含其他信息。
	mode := perm
	f, err := os.OpenFile(filepath.Clean(dst)+"-go-tmp-umask", os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm)
	if err == nil {
		fi, err := f.Stat()
		if err == nil {
			mode = fi.Mode() & 0777
		}
		name := f.Name()
		f.Close()
		os.Remove(name)
	}

	if err := os.Chmod(src, mode); err == nil {
		if err := os.Rename(src, dst); err == nil {
			if cfg.BuildX {
				b.Showcmd("", "mv %s %s", src, dst)
			}
			return nil
		}
	}

	return b.copyFile(dst, src, perm, force)
}

// copyFile类似于“cp src dst”。
func (b *Builder) copyFile(dst, src string, perm fs.FileMode, force bool) error {
	if cfg.BuildN || cfg.BuildX {
		b.Showcmd("", "cp %s %s", src, dst)
		if cfg.BuildN {
			return nil
		}
	}

	sf, err := os.Open(src)
	if err != nil {
		return err
	}
	defer sf.Close()

	// 删除/覆盖dst时要小心。
	// 如果dst存在并且是目录
	// 或非空非对象文件，则不要删除/覆盖。
	if fi, err := os.Stat(dst); err == nil {
		if fi.IsDir() {
			return fmt.Errorf("build output %q already exists and is a directory", dst)
		}
		if !force && fi.Mode().IsRegular() && fi.Size() != 0 && !isObject(dst) {
			return fmt.Errorf("build output %q already exists and is not an object file", dst)
		}
	}

	// 在Windows上，从上次尝试中删除挥之不去的~文件。
	if base.ToolIsWindows {
		if _, err := os.Stat(dst + "~"); err == nil {
			os.Remove(dst + "~")
		}
	}

	mayberemovefile(dst)
	df, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
	if err != nil && base.ToolIsWindows {
		// Windows不允许在二进制文件
		// 执行时删除该文件。试着把它移开。
		// 如果移动失败（很可能），我们将在下次安装此二进制文件时重试
		// 。
		if err := os.Rename(dst, dst+"~"); err == nil {
			os.Remove(dst + "~")
		}
		df, err = os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
	}
	if err != nil {
		return fmt.Errorf("copying %s: %w", src, err) // 错误应该已经参考dst 
	}

	_, err = io.Copy(df, sf)
	df.Close()
	if err != nil {
		mayberemovefile(dst)
		return fmt.Errorf("copying %s to %s: %v", src, dst, err)
	}
	return nil
}

// writeFile将文本写入文件。
func (b *Builder) writeFile(file string, text []byte) error {
	if cfg.BuildN || cfg.BuildX {
		b.Showcmd("", "cat >%s << 'EOF' # internal\n%sEOF", file, text)
	}
	if cfg.BuildN {
		return nil
	}
	return os.WriteFile(file, text, 0666)
}

// 安装cgo导出头文件（如果有）。
func (b *Builder) installHeader(ctx context.Context, a *Action) error {
	src := a.Objdir + "_cgo_install.h"
	if _, err := os.Stat(src); os.IsNotExist(err) {
		// 如果文件不存在，则不存在导出的
		// 函数，我们不安装任何东西。
		// TODO（rsc）：一旦我们知道缓存正在正确的时间重建
		// （不丢失重建），这里我们应该删除安装的头，如果有的话。
		if cfg.BuildX {
			b.Showcmd("", "# %s not created", src)
		}
		return nil
	}

	if err := allowInstall(a); err != nil {
		return err
	}

	dir, _ := filepath.Split(a.Target)
	if dir != "" {
		if err := b.Mkdir(dir); err != nil {
			return err
		}
	}

	return b.moveOrCopyFile(a.Target, src, 0666, true)
}

// cover运行，实际上，
// go tool cover-mode=b.coverMode-var=“varName”-o dst。去src。go 
func (b *Builder) cover(a *Action, dst, src string, varName string) error {
	return b.run(a, a.Objdir, "cover "+a.Package.ImportPath, nil,
		cfg.BuildToolexec,
		base.Tool("cover"),
		"-mode", a.Package.Internal.CoverMode,
		"-var", varName,
		"-o", dst,
		src)
}

var objectMagic = [][]byte{
	{'!', '<', 'a', 'r', 'c', 'h', '>', '\n'}, // 软件包存档
	{'<', 'b', 'i', 'g', 'a', 'f', '>', '\n'}, // 软件包AIX大存档
	{'\x7F', 'E', 'L', 'F'},                   // ELF 
	{0xFE, 0xED, 0xFA, 0xCE},                  // Mach-O大端32位
	{0xFE, 0xED, 0xFA, 0xCF},                  // Mach-O大端64位
	{0xCE, 0xFA, 0xED, 0xFE},                  // Mach-O小端32位
	{0xCF, 0xFA, 0xED, 0xFE},                  // Mach-O小端64位
	{0x4d, 0x5a, 0x90, 0x00, 0x03, 0x00},      // 由6l和gcc 
	{0x4d, 0x5a, 0x78, 0x00, 0x01, 0x00},      // llvm为dll 
	{0x00, 0x00, 0x01, 0xEB},                  // Plan 9 i386 
	{0x00, 0x00, 0x8a, 0x97},                  // Plan 9 amd64 
	{0x00, 0x00, 0x06, 0x47},                  // Plan 9 arm 
	{0x00, 0x61, 0x73, 0x6D},                  // WASM 
	{0x01, 0xDF},                              // XCOFF 32位
	{0x01, 0xF7},                              // XCOFF 64位
}

func isObject(s string) bool {
	f, err := os.Open(s)
	if err != nil {
		return false
	}
	defer f.Close()
	buf := make([]byte, 64)
	io.ReadFull(f, buf)
	for _, magic := range objectMagic {
		if bytes.HasPrefix(buf, magic) {
			return true
		}
	}
	return false
}

// 如果有足够的权限，我们可能会删除
// 甚至是设备文件，例如，这是无意的。
func mayberemovefile(s string) {
	if fi, err := os.Lstat(s); err == nil && !fi.Mode().IsRegular() {
		return
	}
	os.Remove(s)
}

// fmtcmd以fmt的方式格式化命令。Sprintf，但也有：
// 
// 如果dir为非空且脚本现在不在dir中，则
// fmtcmd在命令前插入“cd dir\n”。
// 
// fmtcmd将b.WorkDir的值替换为$WORK。
// fmtcmd将goroot的值替换为$goroot。
// fmtcmd将b.gobin的值替换为$gobin。
// 
// fmtcmd将当前目录的名称替换为点（.）
// 但仅当它位于空格分隔令牌的开头时。
// 
func (b *Builder) fmtcmd(dir string, format string, args ...any) string {
	cmd := fmt.Sprintf(format, args...)
	if dir != "" && dir != "/" {
		dot := " ."
		if dir[len(dir)-1] == filepath.Separator {
			dot += string(filepath.Separator)
		}
		cmd = strings.ReplaceAll(" "+cmd, " "+dir, dot)[1:]
		if b.scriptDir != dir {
			b.scriptDir = dir
			cmd = "cd " + dir + "\n" + cmd
		}
	}
	if b.WorkDir != "" {
		cmd = strings.ReplaceAll(cmd, b.WorkDir, "$WORK")
		escaped := strconv.Quote(b.WorkDir)
		escaped = escaped[1 : len(escaped)-1] // 带引号字符
		if escaped != b.WorkDir {
			cmd = strings.ReplaceAll(cmd, escaped, "$WORK")
		}
	}
	return cmd
}

// showcmd将给定命令打印到标准输出
// 用于实现-n或-x。
func (b *Builder) Showcmd(dir string, format string, args ...any) {
	b.output.Lock()
	defer b.output.Unlock()
	b.Print(b.fmtcmd(dir, format, args...) + "\n")
}

// showOutput打印“#desc”，后跟给定输出。
// 输出应包含对“dir”的引用，通常是
// 未能生成的包的源目录。
// showOutput重写了dir的相对路径，当相对路径较短时，指向dir。这通常更令人愉快。例如，如果fmt没有编译，我们使用src/html，那么
// 输出是
// 
// /$go build 
// /#fmt 
// fmt/打印。go:1090:未定义：asdf 
// /$
// 
// 代替
// 
// /$go build 
// /#fmt 
// /usr/gopher/go src/fmt/print。go:1090:未定义：asdf 
// $
// 
// showOutput还将工作目录的引用替换为$work。
// 
// 如果a不为零且a.output不为零，则showOutput将附加到该切片，而不是
// 打印到b.Print。
// 
func (b *Builder) showOutput(a *Action, dir, desc, out string) {
	prefix := "# " + desc
	suffix := "\n" + out
	if reldir := base.ShortPath(dir); reldir != dir {
		suffix = strings.ReplaceAll(suffix, " "+dir, " "+reldir)
		suffix = strings.ReplaceAll(suffix, "\n"+dir, "\n"+reldir)
		suffix = strings.ReplaceAll(suffix, "\n\t"+dir, "\n\t"+reldir)
	}
	suffix = strings.ReplaceAll(suffix, " "+b.WorkDir, " $WORK")

	if a != nil && a.output != nil {
		a.output = append(a.output, prefix...)
		a.output = append(a.output, suffix...)
		return
	}

	b.output.Lock()
	defer b.output.Unlock()
	b.Print(prefix, suffix)
}

// errPrintedOutput是一个特殊错误，表明命令失败了
// 但它也生成了输出，并且输出已经打印了
// 因此没有必要显示“退出状态1”或任何
// 等待状态。主要执行人，建筑商。做，知道不要
// 打印此错误。
var errPrintedOutput = errors.New("already printed output - no need to show error")

var cgoLine = lazyregexp.New(`\[[^\[\]]+\.(cgo1|cover)\.go:[0-9]+(:[0-9]+)?\]`)
var cgoTypeSigRe = lazyregexp.New(`\b_C2?(type|func|var|macro)_\B`)

// run在目录dir中运行cmdline给出的命令。
// 如果命令失败，run将打印有关失败的信息
// 并返回非零错误。
func (b *Builder) run(a *Action, dir string, desc string, env []string, cmdargs ...any) error {
	out, err := b.runOut(a, dir, env, cmdargs...)
	if len(out) > 0 {
		if desc == "" {
			desc = b.fmtcmd(dir, "%s", strings.Join(str.StringList(cmdargs...), " "))
		}
		b.showOutput(a, dir, desc, b.processOutput(out))
		if err != nil {
			err = errPrintedOutput
		}
	}
	return err
}

// processOutput准备输出到控制台的输出。
func (b *Builder) processOutput(out []byte) string {
	if out[len(out)-1] != '\n' {
		out = append(out, '\n')
	}
	messages := string(out)
	// 修复引用cgo生成代码的输出，以提高可读性。
	// 将x.go:19[/tmp/../x.cgo1.go:18]替换为x.go:19。
	// 用*[100]C.foo替换*[100]_Ctype_foo。
	// 如果我们使用的是-x，那么假设我们正在调试并且想要完全转储，所以禁用重写。
	if !cfg.BuildX && cgoLine.MatchString(messages) {
		messages = cgoLine.ReplaceAllString(messages, "")
		messages = cgoTypeSigRe.ReplaceAllString(messages, "C.")
	}
	return messages
}

// runOut在目录dir中运行cmdline给出的命令。
// 返回命令输出和发生的任何错误。
// 它在a中累积执行时间。
func (b *Builder) runOut(a *Action, dir string, env []string, cmdargs ...any) ([]byte, error) {
	cmdline := str.StringList(cmdargs...)

	for _, arg := range cmdline {
		// GNU binutils命令，包括gcc和gccgo，将命令行中任意位置（甚至后面的--）的参数
		// /@foo解释为“读取并插入名为foo的文件中的参数”
		// 不要说任何可能被误解的话。
		if strings.HasPrefix(arg, "@") {
			return nil, fmt.Errorf("invalid command-line argument %s in command: %s", arg, joinUnambiguously(cmdline))
		}
	}

	if cfg.BuildN || cfg.BuildX {
		var envcmdline string
		for _, e := range env {
			if j := strings.IndexByte(e, '='); j != -1 {
				if strings.ContainsRune(e[j+1:], '\'') {
					envcmdline += fmt.Sprintf("%s=%q", e[:j], e[j+1:])
				} else {
					envcmdline += fmt.Sprintf("%s='%s'", e[:j], e[j+1:])
				}
				envcmdline += " "
			}
		}
		envcmdline += joinUnambiguously(cmdline)
		b.Showcmd(dir, "%s", envcmdline)
		if cfg.BuildN {
			return nil, nil
		}
	}

	var buf bytes.Buffer
	cmd := exec.Command(cmdline[0], cmdline[1:]...)
	if cmd.Path != "" {
		cmd.Args[0] = cmd.Path
	}
	cmd.Stdout = &buf
	cmd.Stderr = &buf
	cleanup := passLongArgsInResponseFiles(cmd)
	defer cleanup()
	cmd.Dir = dir
	cmd.Env = base.AppendPWD(os.Environ(), cmd.Dir)

	// 为-TOOLEXEC tools添加TOOLEXEC_IMPORTPATH环境变量。
	// 如果没有使用-toolexec，这其实并不重要。
	// 注意a.Package。Desc实际上不是一个导入路径，
	// 但这与“go list-f{{.ImportPath}”是一致的。
	// 另外，在“go list-json”中唯一地标识包是很有用的。
	if a != nil && a.Package != nil {
		cmd.Env = append(cmd.Env, "TOOLEXEC_IMPORTPATH="+a.Package.Desc())
	}

	cmd.Env = append(cmd.Env, env...)
	start := time.Now()
	err := cmd.Run()
	if a != nil && a.json != nil {
		aj := a.json
		aj.Cmd = append(aj.Cmd, joinUnambiguously(cmdline))
		aj.CmdReal += time.Since(start)
		if ps := cmd.ProcessState; ps != nil {
			aj.CmdUser += ps.UserTime()
			aj.CmdSys += ps.SystemTime()
		}
	}

	// err可以是类似于“退出状态1”的内容。
	// 添加有关正在运行的程序的信息。
	// 请注意，如果buf。Bytes（）为非空，调用方通常为
	// 显示buf。字节（）并且根本不会打印错误，因此这里的
	// 前缀不会使大多数输出变得更加详细。
	if err != nil {
		err = errors.New(cmdline[0] + ": " + err.Error())
	}
	return buf.Bytes(), err
}

// join明确打印切片，在必要时引用以使
// 输出明确。
// 待办事项：见第5279期。命令的打印需要完全重做。
func joinUnambiguously(a []string) string {
	var buf bytes.Buffer
	for i, s := range a {
		if i > 0 {
			buf.WriteByte(' ')
		}
		q := strconv.Quote(s)
		// gccgo命令行可以包含-（和-）。
		// 请确保我们对它们进行了报价，因为它们是壳牌公司特有的。
		// trimpath参数还可以包含>（部分=>）和；。引用这些。
		if s == "" || strings.ContainsAny(s, " ()>;") || len(q) > len(s)+2 {
			buf.WriteString(q)
		} else {
			buf.WriteString(s)
		}
	}
	return buf.String()
}

// cCompilerEnv返回运行
// C编译器时要设置的环境变量。这是为了禁用叮当声错误
// 消息中的转义码，这些消息会混淆cgo等工具。
func (b *Builder) cCompilerEnv() []string {
	return []string{"TERM=dumb"}
}

// mkdir生成命名目录。
func (b *Builder) Mkdir(dir string) error {
	// 当a.Objdir==”时，将Mkdir（a.Objdir）设为no op而不是错误。
	if dir == "" {
		return nil
	}

	b.exec.Lock()
	defer b.exec.Unlock()
	// 我们可以对成为
	// 确定目录存在。跳过重复的通话。
	if b.mkdirCache[dir] {
		return nil
	}
	b.mkdirCache[dir] = true

	if cfg.BuildN || cfg.BuildX {
		b.Showcmd("", "mkdir -p %s", dir)
		if cfg.BuildN {
			return nil
		}
	}

	if err := os.MkdirAll(dir, 0777); err != nil {
		return err
	}
	return nil
}

// symlink创建一个symlink newname->oldname。
func (b *Builder) Symlink(oldname, newname string) error {
	// 尝试重新创建现有符号链接不是错误。
	if link, err := os.Readlink(newname); err == nil && link == oldname {
		return nil
	}

	if cfg.BuildN || cfg.BuildX {
		b.Showcmd("", "ln -s %s %s", oldname, newname)
		if cfg.BuildN {
			return nil
		}
	}
	return os.Symlink(oldname, newname)
}

// mkAbs返回与目录dir中的
// f相对应的绝对路径。
// 我们总是传递源文件的绝对路径，因此
// 错误消息将包括需要注意的文件的完整路径
// 的完整路径。
func mkAbs(dir, f string) string {
	// 不要使用绝对路径。
	// 此外，在-n模式下，我们使用伪目录$WORK 
	// 而不是创建一个不会使用的实际工作目录。
	// 也不要使用以$WORK开头的路径。
	if filepath.IsAbs(f) || strings.HasPrefix(f, "$WORK") {
		return f
	}
	return filepath.Join(dir, f)
}

type toolchain interface {
	// gc在一组文件的特定目录中运行编译器
	// 并返回生成的输出文件的名称。
	gc(b *Builder, a *Action, archive string, importcfg, embedcfg []byte, symabis string, asmhdr bool, gofiles []string) (ofile string, out []byte, err error)
	// cc在C文件的目录中运行工具链的C编译器以生成输出文件。
	cc(b *Builder, a *Action, ofile, cfile string) error
	// asm在特定文件的特定目录中运行汇编程序
	// 并返回命名输出文件的列表。
	asm(b *Builder, a *Action, sfiles []string) ([]string, error)
	// symabis扫描sfiles中的符号ABI，并返回输出符号ABIs文件的
	// 路径，如果没有，则返回“”。
	symabis(b *Builder, a *Action, sfiles []string) (string, error)
	// pack在特定目录中运行归档打包程序，以从一组目标文件创建一个归档文件。
	// 通常在对象目录中运行。
	pack(b *Builder, a *Action, afile string, ofiles []string) error
	// ld运行链接器以创建一个从mainpkg开始的可执行文件。
	ld(b *Builder, root *Action, out, importcfg, mainpkg string) error
	// ldShared运行链接器以创建包含toplevelactions构建的PKG的共享库
	ldShared(b *Builder, root *Action, toplevelactions []*Action, out, importcfg string, allactions []*Action) error

	compiler() string
	linker() string
}

type noToolchain struct{}

func noCompiler() error {
	log.Fatalf("unknown compiler %q", cfg.BuildContext.Compiler)
	return nil
}

func (noToolchain) compiler() string {
	noCompiler()
	return ""
}

func (noToolchain) linker() string {
	noCompiler()
	return ""
}

func (noToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg []byte, symabis string, asmhdr bool, gofiles []string) (ofile string, out []byte, err error) {
	return "", nil, noCompiler()
}

func (noToolchain) asm(b *Builder, a *Action, sfiles []string) ([]string, error) {
	return nil, noCompiler()
}

func (noToolchain) symabis(b *Builder, a *Action, sfiles []string) (string, error) {
	return "", noCompiler()
}

func (noToolchain) pack(b *Builder, a *Action, afile string, ofiles []string) error {
	return noCompiler()
}

func (noToolchain) ld(b *Builder, root *Action, out, importcfg, mainpkg string) error {
	return noCompiler()
}

func (noToolchain) ldShared(b *Builder, root *Action, toplevelactions []*Action, out, importcfg string, allactions []*Action) error {
	return noCompiler()
}

func (noToolchain) cc(b *Builder, a *Action, ofile, cfile string) error {
	return noCompiler()
}

// gcc运行gcc编译器以从单个C文件创建对象。ABCFDG 
func (b *Builder) gcc(a *Action, p *load.Package, workdir, out string, flags []string, cfile string) error {
	return b.ccompile(a, p, out, flags, cfile, b.GccCmd(p.Dir, workdir))
}

func (b *Builder) gxx(a *Action, p *load.Package, workdir, out string, flags []string, cxxfile string) error {
	return b.ccompile(a, p, out, flags, cxxfile, b.GxxCmd(p.Dir, workdir))
}

// gfortran运行gfortran Fortran编译器，从单个Fortran文件创建对象。ABCFDG 
func (b *Builder) gfortran(a *Action, p *load.Package, workdir, out string, flags []string, ffile string) error {
	return b.ccompile(a, p, out, flags, ffile, b.gfortranCmd(p.Dir, workdir))
}

func (b *Builder) ccompile(a *Action, p *load.Package, outfile string, flags []string, file string, compiler []string) error {
	file = mkAbs(p.Dir, file)
	desc := p.ImportPath
	outfile = mkAbs(p.Dir, outfile)

	// 如果设置了-trimpath或GOROOT_FINAL，则删除源目录路径。
	// 这是源文件所需的（例如，包目录中的.c文件）。
	// TODO（golang.org/issue/36072）：cgo还生成带有指向源目录的#行
	// 指令的文件。当启用-trimpath时，它不应该生成这些
	// 。
	if b.gccSupportsFlag(compiler, "-fdebug-prefix-map=a=b") {
		if cfg.BuildTrimpath || p.Goroot {
			// 与行动保持同步。trimpath。
			// 修剪后的路径有点不同，但我们需要在
			// 相同的情况下进行修剪。
			var from, toPath string
			if m := p.Module; m != nil {
				from = m.Dir
				toPath = m.Path + "@" + m.Version
			} else {
				from = p.Dir
				toPath = p.ImportPath
			}
			// -fdebug前缀映射需要一个绝对的“to”路径（或者它将路径
			// 与工作目录连接起来）。选择对
			// 目标平台有意义的东西。
			var to string
			if cfg.BuildContext.GOOS == "windows" {
				to = filepath.Join(`\\_\_`, toPath)
			} else {
				to = filepath.Join("/_", toPath)
			}
			flags = append(flags[:len(flags):len(flags)], "-fdebug-prefix-map="+from+"="+to)
		}
	}

	overlayPath := file
	if p, ok := a.nonGoOverlay[overlayPath]; ok {
		overlayPath = p
	}
	output, err := b.runOut(a, filepath.Dir(overlayPath), b.cCompilerEnv(), compiler, flags, "-o", outfile, "-c", filepath.Base(overlayPath))
	if len(output) > 0 {
		// 在FreeBSD 11上，当我们将-g传递给clang 3.8时，它将用-dwarf version=2调用其内部汇编程序。
		// 当它看到的时候。部分笔记GNU堆栈，它警告
		// /“DWARF2每个编译单元只支持一个节”。
		// 这个警告毫无意义，因为这个部分是空的，但是它让人困惑。
		// 我们通过检测警告
		// 并删除-g并重试来解决这个问题。
		if bytes.Contains(output, []byte("DWARF2 only supports one section per compilation unit")) {
			newFlags := make([]string, 0, len(flags))
			for _, f := range flags {
				if !strings.HasPrefix(f, "-g") {
					newFlags = append(newFlags, f)
				}
			}
			if len(newFlags) < len(flags) {
				return b.ccompile(a, p, outfile, newFlags, file, compiler)
			}
		}

		b.showOutput(a, p.Dir, desc, b.processOutput(output))
		if err != nil {
			err = errPrintedOutput
		} else if os.Getenv("GO_BUILDER_NAME") != "" {
			return errors.New("C compiler warning promoted to error on Go builders")
		}
	}
	return err
}

// gccld运行gcc链接器，从一组对象文件创建一个可执行文件。
func (b *Builder) gccld(a *Action, p *load.Package, objdir, outfile string, flags []string, objs []string) error {
	var cmd []string
	if len(p.CXXFiles) > 0 || len(p.SwigCXXFiles) > 0 {
		cmd = b.GxxCmd(p.Dir, objdir)
	} else {
		cmd = b.GccCmd(p.Dir, objdir)
	}

	cmdargs := []any{cmd, "-o", outfile, objs, flags}
	dir := p.Dir
	out, err := b.runOut(a, base.Cwd(), b.cCompilerEnv(), cmdargs...)

	if len(out) > 0 {
		// 过滤掉因Go之外的错误而导致的无用链接器警告。
		// 另请参见cmd/link/internal/ld的hostlink方法。
		var save [][]byte
		var skipLines int
		for _, line := range bytes.SplitAfter(out, []byte("\n")) {
			// golang。org/issue/26073-Apple Xcode bug 
			if bytes.Contains(line, []byte("ld: warning: text-based stub file")) {
				continue
			}

			if skipLines > 0 {
				skipLines--
				continue
			}

			// 在AIX上用runtime/cgo删除重复的主符号。
			// 在运行时/cgo中，有两个主要选项可用：
			// 一个由cgo工具使用{return 0；}生成。
			// 另一个是主调用运行时。rt0_go 
			// 在运行时/cgo中。
			// 第二个不能被cgo程序使用，因为
			// 运行时。rt0_go对他们来说是未知的。
			// 因此，我们让ld删除这个主版本
			// 并使用cgo生成的版本。
			if p.ImportPath == "runtime/cgo" && bytes.Contains(line, []byte("ld: 0711-224 WARNING: Duplicate symbol: .main")) {
				skipLines = 1
				continue
			}

			save = append(save, line)
		}
		out = bytes.Join(save, nil)
		if len(out) > 0 {
			b.showOutput(nil, dir, p.ImportPath, b.processOutput(out))
			if err != nil {
				err = errPrintedOutput
			}
		}
	}
	return err
}

// gccCmd返回gcc命令行前缀
// defaultCC在zdefaultcc中定义。go，由cmd/dist编写。
func (b *Builder) GccCmd(incdir, workdir string) []string {
	return b.compilerCmd(b.ccExe(), incdir, workdir)
}

// gxxCmd返回一个g++命令行前缀
// defaultCXX在zdefaultcc中定义。go，由cmd/dist.
func (b *Builder) GxxCmd(incdir, workdir string) []string {
	return b.compilerCmd(b.cxxExe(), incdir, workdir)
}

// gfortranCmd编写，返回gfortran命令行前缀。
func (b *Builder) gfortranCmd(incdir, workdir string) []string {
	return b.compilerCmd(b.fcExe(), incdir, workdir)
}

// ccExe返回CC编译器设置，不包含我们隐式添加的所有额外标志。
func (b *Builder) ccExe() []string {
	return envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch))
}

// cxxExe返回CXX编译器设置，不包含我们隐式添加的所有额外标志。
func (b *Builder) cxxExe() []string {
	return envList("CXX", cfg.DefaultCXX(cfg.Goos, cfg.Goarch))
}

// fcExe返回FC编译器设置，不包含我们隐式添加的所有额外标志。
func (b *Builder) fcExe() []string {
	return envList("FC", "gfortran")
}

// 编译器cmd返回给定环境的命令行前缀
// 变量，并在变量为空时使用默认命令。
func (b *Builder) compilerCmd(compiler []string, incdir, workdir string) []string {
	a := append(compiler, "-I", incdir)

	// 当然想要-fPIC，但在Windows上，gcc抱怨
	// “-fPIC被目标忽略（所有代码都是位置独立的）”
	if cfg.Goos != "windows" {
		a = append(a, "-fPIC")
	}
	a = append(a, b.gccArchArgs()...)
	// gcc-4.5及更高版本需要显式“-pthread”标志
	// 用于带pthread库的多线程处理。
	if cfg.BuildContext.CgoEnabled {
		switch cfg.Goos {
		case "windows":
			a = append(a, "-mthreads")
		default:
			a = append(a, "-pthread")
		}
	}

	if cfg.Goos == "aix" {
		// mcmodel=large必须始终启用才能允许大TOC。
		a = append(a, "-mcmodel=large")
	}

	// 在clang错误中禁用ASCII art，如果可能的话
	if b.gccSupportsFlag(compiler, "-fno-caret-diagnostics") {
		a = append(a, "-fno-caret-diagnostics")
	}
	// clang在命令行参数方面太聪明了
	if b.gccSupportsFlag(compiler, "-Qunused-arguments") {
		a = append(a, "-Qunused-arguments")
	}

	// 在错误消息中禁用文字包装
	a = append(a, "-fmessage-length=0")

	// 告诉gcc不要在对象文件中包含工作目录。
	if b.gccSupportsFlag(compiler, "-fdebug-prefix-map=a=b") {
		if workdir == "" {
			workdir = b.WorkDir
		}
		workdir = strings.TrimSuffix(workdir, string(filepath.Separator))
		a = append(a, "-fdebug-prefix-map="+workdir+"=/tmp/go-build")
	}

	// 告诉gcc不要在对象文件中包含标志，这会破坏上面的-fdebug前缀映射点。
	if b.gccSupportsFlag(compiler, "-gno-record-gcc-switches") {
		a = append(a, "-gno-record-gcc-switches")
	}

	// 在OS X上，一些编译器的行为就好像总是设置了-fno common 
	// 一样，6l/8l中的Mach-O链接器假设了这一点。
	// 请参阅https:
	if cfg.Goos == "darwin" || cfg.Goos == "ios" {
		a = append(a, "-fno-common")
	}

	return a
}

// gccNoPie返回用于请求非派的标志。在默认启用PIE（位置独立可执行文件）的系统上，
// /-在与-Wl，-r进行部分链接时，必须不传递PIE。
// 但是，并非所有编译器都支持-no-PIE，而且clang拼写为-nopie。
func (b *Builder) gccNoPie(linker []string) string {
	if b.gccSupportsFlag(linker, "-no-pie") {
		return "-no-pie"
	}
	if b.gccSupportsFlag(linker, "-nopie") {
		return "-nopie"
	}
	return ""
}

// gccSupportsFlag检查编译器是否支持标志。
func (b *Builder) gccSupportsFlag(compiler []string, flag string) bool {
	key := [2]string{compiler[0], flag}

	b.exec.Lock()
	defer b.exec.Unlock()
	if b, ok := b.flagCache[key]; ok {
		return b
	}
	if b.flagCache == nil {
		b.flagCache = make(map[[2]string]bool)
	}

	tmp := os.DevNull
	if runtime.GOOS == "windows" {
		f, err := os.CreateTemp(b.WorkDir, "")
		if err != nil {
			return false
		}
		f.Close()
		tmp = f.Name()
		defer os.Remove(tmp)
	}

	// 我们以前写一个空的C文件，但这会让
	// go build-n变得复杂。我们尝试使用一个不存在的文件，但
	// 在GCC版本为4.2.1的系统上失败；这是GCC的最后一个GPLv2 
	// 版本，所以一些系统已经冻结在它上面。
	// 现在我们在stdin上传递一个空文件，它至少应该适用于
	// GCC和clang。
	cmdArgs := str.StringList(compiler, flag, "-c", "-x", "c", "-", "-o", tmp)
	if cfg.BuildN || cfg.BuildX {
		b.Showcmd(b.WorkDir, "%s || true", joinUnambiguously(cmdArgs))
		if cfg.BuildN {
			return false
		}
	}
	cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
	cmd.Dir = b.WorkDir
	cmd.Env = base.AppendPWD(os.Environ(), cmd.Dir)
	cmd.Env = append(cmd.Env, "LC_ALL=C")
	out, _ := cmd.CombinedOutput()
	// GCC表示“无法识别的命令行选项”。
	// clang说“未知参数”。
	// GCC的旧版本显示“无法识别的调试输出级别”。
	// For-fsplit stack GCC表示“'-fsplit stack'不受支持”。
	supported := !bytes.Contains(out, []byte("unrecognized")) &&
		!bytes.Contains(out, []byte("unknown")) &&
		!bytes.Contains(out, []byte("unrecognised")) &&
		!bytes.Contains(out, []byte("is not supported"))
	b.flagCache[key] = supported
	return supported
}

// gccArchArgs根据架构返回要传递给gcc的参数。
func (b *Builder) gccArchArgs() []string {
	switch cfg.Goarch {
	case "386":
		return []string{"-m32"}
	case "amd64":
		if cfg.Goos == "darwin" {
			return []string{"-arch", "x86_64", "-m64"}
		}
		return []string{"-m64"}
	case "arm64":
		if cfg.Goos == "darwin" {
			return []string{"-arch", "arm64"}
		}
	case "arm":
		return []string{"-marm"} // not thumb 
	case "s390x":
		return []string{"-m64", "-march=z196"}
	case "mips64", "mips64le":
		args := []string{"-mabi=64"}
		if cfg.GOMIPS64 == "hardfloat" {
			return append(args, "-mhard-float")
		} else if cfg.GOMIPS64 == "softfloat" {
			return append(args, "-msoft-float")
		}
	case "mips", "mipsle":
		args := []string{"-mabi=32", "-march=mips32"}
		if cfg.GOMIPS == "hardfloat" {
			return append(args, "-mhard-float", "-mfp32", "-mno-odd-spreg")
		} else if cfg.GOMIPS == "softfloat" {
			return append(args, "-msoft-float")
		}
	case "ppc64":
		if cfg.Goos == "aix" {
			return []string{"-maix64"}
		}
	}
	return nil
}

// envList返回给定环境变量的值
// 输入字段，变量为空时使用默认值。
// 
// 对于
// str.SplitQuotedFields，必须正确引用环境变量。这应该在构建
// /任何东西之前完成，例如，在BuildInit中。ABCFDG
func envList(key, def string) []string {
	v := cfg.Getenv(key)
	if v == "" {
		v = def
	}
	args, err := quoted.Split(v)
	if err != nil {
		panic(fmt.Sprintf("could not parse environment variable %s with value %q: %v", key, v, err))
	}
	return args
}

func (b *Builder) CFlags(p *load.Package) (cppflags, cflags, cxxflags, fflags, ldflags []string, err error) {
	defaults := "-g -O2"

	if cppflags, err = buildFlags("CPPFLAGS", "", p.CgoCPPFLAGS, checkCompilerFlags); err != nil {
		return
	}
	if cflags, err = buildFlags("CFLAGS", defaults, p.CgoCFLAGS, checkCompilerFlags); err != nil {
		return
	}
	if cxxflags, err = buildFlags("CXXFLAGS", defaults, p.CgoCXXFLAGS, checkCompilerFlags); err != nil {
		return
	}
	if fflags, err = buildFlags("FFLAGS", defaults, p.CgoFFLAGS, checkCompilerFlags); err != nil {
		return
	}
	if ldflags, err = buildFlags("LDFLAGS", defaults, p.CgoLDFLAGS, checkLinkerFlags); err != nil {
		return
	}

	return
}

func buildFlags(name, defaults string, fromPackage []string, check func(string, string, []string) error) ([]string, error) {
	if err := check(name, "#cgo "+name, fromPackage); err != nil {
		return nil, err
	}
	return str.StringList(envList("CGO_"+name, defaults), fromPackage), nil
}

var cgoRe = lazyregexp.New(`[/\\:]`)

func (b *Builder) cgo(a *Action, cgoExe, objdir string, pcCFLAGS, pcLDFLAGS, cgofiles, gccfiles, gxxfiles, mfiles, ffiles []string) (outGo, outObj []string, err error) {
	p := a.Package
	cgoCPPFLAGS, cgoCFLAGS, cgoCXXFLAGS, cgoFFLAGS, cgoLDFLAGS, err := b.CFlags(p)
	if err != nil {
		return nil, nil, err
	}

	cgoCPPFLAGS = append(cgoCPPFLAGS, pcCFLAGS...)
	cgoLDFLAGS = append(cgoLDFLAGS, pcLDFLAGS...)
	// 如果我们正在编译Objective-C代码，那么我们需要与libobjc链接
	if len(mfiles) > 0 {
		cgoLDFLAGS = append(cgoLDFLAGS, "-lobjc")
	}

	// 同样适用于Fortran，除了有许多Fortran编译器。
	// 支持GfortRun out the box并让其他人通过CGO_LDFLAGS传递正确的链接选项
	// 
	if len(ffiles) > 0 {
		fc := cfg.Getenv("FC")
		if fc == "" {
			fc = "gfortran"
		}
		if strings.Contains(fc, "gfortran") {
			cgoLDFLAGS = append(cgoLDFLAGS, "-lgfortran")
		}
	}

	if cfg.BuildMSan {
		cgoCFLAGS = append([]string{"-fsanitize=memory"}, cgoCFLAGS...)
		cgoLDFLAGS = append([]string{"-fsanitize=memory"}, cgoLDFLAGS...)
	}
	if cfg.BuildASan {
		cgoCFLAGS = append([]string{"-fsanitize=address"}, cgoCFLAGS...)
		cgoLDFLAGS = append([]string{"-fsanitize=address"}, cgoLDFLAGS...)
	}

	// 允许包括_CGO_导出。h、 以及用户的。h文件，
	// 来自。[ch]包中的文件。
	cgoCPPFLAGS = append(cgoCPPFLAGS, "-I", objdir)

	// cgo 
	// TODO:cgo_标志？
	gofiles := []string{objdir + "_cgo_gotypes.go"}
	cfiles := []string{"_cgo_export.c"}
	for _, fn := range cgofiles {
		f := strings.TrimSuffix(filepath.Base(fn), ".go")
		gofiles = append(gofiles, objdir+f+".cgo1.go")
		cfiles = append(cfiles, f+".cgo2.c")
	}

	// TODO:让cgo不依赖$GOARCH？

	cgoflags := []string{}
	if p.Standard && p.ImportPath == "runtime/cgo" {
		cgoflags = append(cgoflags, "-import_runtime_cgo=false")
	}
	if p.Standard && (p.ImportPath == "runtime/race" || p.ImportPath == "runtime/msan" || p.ImportPath == "runtime/cgo" || p.ImportPath == "runtime/asan") {
		cgoflags = append(cgoflags, "-import_syscall=false")
	}

	// 用p.CgoLDFLAGS更新$CGO_LDFLAGS。
	// 这些标志被记录在生成的_cgo_gotypes中。go文件
	// 使用
	// 包的对象文件，然后go链接器将它们传递给主机链接器。此时，代码中的cgoLDFLAGS 
	// 由原始$CGO_LDFLAGS（未选中）和源代码中的所有
	// 标志组成（选中）。
	cgoenv := b.cCompilerEnv()
	if len(cgoLDFLAGS) > 0 {
		flags := make([]string, len(cgoLDFLAGS))
		for i, f := range cgoLDFLAGS {
			flags[i] = strconv.Quote(f)
		}
		cgoenv = append(cgoenv, "CGO_LDFLAGS="+strings.Join(flags, " "))
	}

	if cfg.BuildToolchainName == "gccgo" {
		if b.gccSupportsFlag([]string{BuildToolchain.compiler()}, "-fsplit-stack") {
			cgoCFLAGS = append(cgoCFLAGS, "-fsplit-stack")
		}
		cgoflags = append(cgoflags, "-gccgo")
		if pkgpath := gccgoPkgpath(p); pkgpath != "" {
			cgoflags = append(cgoflags, "-gccgopkgpath="+pkgpath)
		}
	}

	switch cfg.BuildBuildmode {
	case "c-archive", "c-shared":
		// 告诉cgo，如果有任何导出的函数
		// 它应该生成一个头文件，C代码可以包含这个头文件。
		cgoflags = append(cgoflags, "-exportheader="+objdir+"_cgo_install.h")
	}

	execdir := p.Dir

	// 重写cgo文件中的覆盖路径。
	// cgo添加了
	var trimpath []string
	for i := range cgofiles {
		path := mkAbs(p.Dir, cgofiles[i])
		if opath, ok := fsys.OverlayPath(path); ok {
			cgofiles[i] = opath
			trimpath = append(trimpath, opath+"=>"+path)
		}
	}
	if len(trimpath) > 0 {
		cgoflags = append(cgoflags, "-trimpath", strings.Join(trimpath, ";"))
	}

	if err := b.run(a, execdir, p.ImportPath, cgoenv, cfg.BuildToolexec, cgoExe, "-objdir", objdir, "-importpath", p.ImportPath, cgoflags, "--", cgoCPPFLAGS, cgoCFLAGS, cgofiles); err != nil {
		return nil, nil, err
	}
	outGo = append(outGo, gofiles...)

	// 使用顺序对象文件名来保持它们的不同
	// 并且足够短，以适合。一个头文件名。
	// 我们不再将它们全部收集到_all中。o、 我们希望
	// 工具能够同时看到。o后缀和唯一名称，所以我们需要使它们足够短，以免在最终存档中被截断。
	oseq := 0
	nextOfile := func() string {
		oseq++
		return objdir + fmt.Sprintf("_x%03d.o", oseq)
	}

	// gcc 
	cflags := str.StringList(cgoCPPFLAGS, cgoCFLAGS)
	for _, cfile := range cfiles {
		ofile := nextOfile()
		if err := b.gcc(a, p, a.Objdir, ofile, cflags, objdir+cfile); err != nil {
			return nil, nil, err
		}
		outObj = append(outObj, ofile)
	}

	for _, file := range gccfiles {
		ofile := nextOfile()
		if err := b.gcc(a, p, a.Objdir, ofile, cflags, file); err != nil {
			return nil, nil, err
		}
		outObj = append(outObj, ofile)
	}

	cxxflags := str.StringList(cgoCPPFLAGS, cgoCXXFLAGS)
	for _, file := range gxxfiles {
		ofile := nextOfile()
		if err := b.gxx(a, p, a.Objdir, ofile, cxxflags, file); err != nil {
			return nil, nil, err
		}
		outObj = append(outObj, ofile)
	}

	for _, file := range mfiles {
		ofile := nextOfile()
		if err := b.gcc(a, p, a.Objdir, ofile, cflags, file); err != nil {
			return nil, nil, err
		}
		outObj = append(outObj, ofile)
	}

	fflags := str.StringList(cgoCPPFLAGS, cgoFFLAGS)
	for _, file := range ffiles {
		ofile := nextOfile()
		if err := b.gfortran(a, p, a.Objdir, ofile, fflags, file); err != nil {
			return nil, nil, err
		}
		outObj = append(outObj, ofile)
	}

	switch cfg.BuildToolchainName {
	case "gc":
		importGo := objdir + "_cgo_import.go"
		if err := b.dynimport(a, p, objdir, importGo, cgoExe, cflags, cgoLDFLAGS, outObj); err != nil {
			return nil, nil, err
		}
		outGo = append(outGo, importGo)

	case "gccgo":
		defunC := objdir + "_cgo_defun.c"
		defunObj := objdir + "_cgo_defun.o"
		if err := BuildToolchain.cc(b, a, defunObj, defunC); err != nil {
			return nil, nil, err
		}
		outObj = append(outObj, defunObj)

	default:
		noCompiler()
	}

	// 仔细检查
	// 编译器只允许在基本名称
	// 以“_cgo_382;”开头的文件中使用此类注释。确保这些文件
	// 中的注释是安全的。这是对以某种方式将
	// /这样的评论走私到cgo生成的文件中的人的支持。
	if cfg.BuildToolchainName == "gc" && !cfg.BuildN {
		var flags []string
		for _, f := range outGo {
			if !strings.HasPrefix(filepath.Base(f), "_cgo_") {
				continue
			}

			src, err := os.ReadFile(f)
			if err != nil {
				return nil, nil, err
			}

			const cgoLdflag = "// go:cgo_ldflag“
			idx := bytes.Index(src, []byte(cgoLdflag))
			for idx >= 0 {
				// 我们正在查看
				// 查找行的开头。
				start := bytes.LastIndex(src[:idx], []byte("\n"))
				if start == -1 {
					start = 0
				}

				// 查找行的结尾。
				end := bytes.Index(src[idx:], []byte("\n"))
				if end == -1 {
					end = len(src)
				} else {
					end += idx
				}

				// 检查行中的第一行注释。
				// 我们不担心
				// 这些通常不会出现在cgo生成的文件
				// 
				commentStart += start
				// 那就是匹配。
				if bytes.HasPrefix(src[commentStart:], []byte(cgoLdflag)) {
					// 拔出国旗，并解开它。
					// 编译器就是这样做的。
					flag := string(src[idx+len(cgoLdflag) : end])
					flag = strings.TrimSpace(flag)
					flag = strings.Trim(flag, `"`)
					flags = append(flags, flag)
				}
				src = src[end:]
				idx = bytes.Index(src, []byte(cgoLdflag))
			}
		}

		// 我们希望在flags中找到cgoLDFLAGS的内容。
		if len(cgoLDFLAGS) > 0 {
		outer:
			for i := range flags {
				for j, f := range cgoLDFLAGS {
					if f != flags[i+j] {
						continue outer
					}
				}
				flags = append(flags[:i], flags[i+len(cgoLDFLAGS):]...)
				break
			}
		}

		if err := checkLinkerFlags("LDFLAGS", "go:cgo_ldflag", flags); err != nil {
			return nil, nil, err
		}
	}

	return outGo, outObj, nil
}

// dynimport创建一个名为importGo的Go源文件，其中包含每个符号或库的
// /Go:cgo_import_动态指令
// /由对象文件outObj动态导入。
func (b *Builder) dynimport(a *Action, p *load.Package, objdir, importGo, cgoExe string, cflags, cgoLDFLAGS, outObj []string) error {
	cfile := objdir + "_cgo_main.c"
	ofile := objdir + "_cgo_main.o"
	if err := b.gcc(a, p, objdir, ofile, cflags, cfile); err != nil {
		return err
	}

	linkobj := str.StringList(ofile, outObj, mkAbsFiles(p.Dir, p.SysoFiles))
	dynobj := objdir + "_cgo_.o"

	ldflags := cgoLDFLAGS
	if (cfg.Goarch == "arm" && cfg.Goos == "linux") || cfg.Goos == "android" {
		if !str.Contains(ldflags, "-no-pie") {
			// 我们需要使用-pie for Linux/ARM来获得准确的导入sym（添加在https:
			// 这似乎已经过时，但我们不想破坏现有版本，这取决于此（问题45940）
			ldflags = append(ldflags, "-pie")
		}
		if str.Contains(ldflags, "-pie") && str.Contains(ldflags, "-static") {
			// /-静态-pie没有意义，并导致链接错误。
			// 第26197期。
			n := make([]string, 0, len(ldflags)-1)
			for _, flag := range ldflags {
				if flag != "-static" {
					n = append(n, flag)
				}
			}
			ldflags = n
		}
	}
	if err := b.gccld(a, p, objdir, dynobj, ldflags, linkobj); err != nil {
		return err
	}

	// cgo-dynimport 
	var cgoflags []string
	if p.Standard && p.ImportPath == "runtime/cgo" {
		cgoflags = []string{"-dynlinker"} // 记录到动态链接器的路径
	}
	return b.run(a, base.Cwd(), p.ImportPath, b.cCompilerEnv(), cfg.BuildToolexec, cgoExe, "-dynpackage", p.Name, "-dynimport", dynobj, "-dynout", importGo, cgoflags)
}

// 在所有SWIG输入文件上运行SWIG。
// TODO:不要构建共享库，一旦SWIG发出必要的
// pragmas用于外部链接。
func (b *Builder) swig(a *Action, p *load.Package, objdir string, pcCFLAGS []string) (outGo, outC, outCXX []string, err error) {
	if err := b.swigVersionCheck(); err != nil {
		return nil, nil, nil, err
	}

	intgosize, err := b.swigIntSize(objdir)
	if err != nil {
		return nil, nil, nil, err
	}

	for _, f := range p.SwigFiles {
		goFile, cFile, err := b.swigOne(a, p, f, objdir, pcCFLAGS, false, intgosize)
		if err != nil {
			return nil, nil, nil, err
		}
		if goFile != "" {
			outGo = append(outGo, goFile)
		}
		if cFile != "" {
			outC = append(outC, cFile)
		}
	}
	for _, f := range p.SwigCXXFiles {
		goFile, cxxFile, err := b.swigOne(a, p, f, objdir, pcCFLAGS, true, intgosize)
		if err != nil {
			return nil, nil, nil, err
		}
		if goFile != "" {
			outGo = append(outGo, goFile)
		}
		if cxxFile != "" {
			outCXX = append(outCXX, cxxFile)
		}
	}
	return outGo, outC, outCXX, nil
}

// 确保SWIG足够新。
var (
	swigCheckOnce sync.Once
	swigCheck     error
)

func (b *Builder) swigDoVersionCheck() error {
	out, err := b.runOut(nil, "", nil, "swig", "-version")
	if err != nil {
		return err
	}
	re := regexp.MustCompile(`[vV]ersion +([\d]+)([.][\d]+)?([.][\d]+)?`)
	matches := re.FindSubmatch(out)
	if matches == nil {
		// 找不到版本号；希望一切顺利。
		return nil
	}

	major, err := strconv.Atoi(string(matches[1]))
	if err != nil {
		// 找不到版本号；希望一切顺利。
		return nil
	}
	const errmsg = "must have SWIG version >= 3.0.6"
	if major < 3 {
		return errors.New(errmsg)
	}
	if major > 3 {
		// 4.0或更高版本
		return nil
	}

	// 我们有SWIG版本3。x、 
	if len(matches[2]) > 0 {
		minor, err := strconv.Atoi(string(matches[2][1:]))
		if err != nil {
			return nil
		}
		if minor > 0 {
			// 3.1或更高版本
			return nil
		}
	}

	// 我们有SWIG版本3.0。x、 
	if len(matches[3]) > 0 {
		patch, err := strconv.Atoi(string(matches[3][1:]))
		if err != nil {
			return nil
		}
		if patch < 6 {
			// 3.0.6之前的版本。
			return errors.New(errmsg)
		}
	}

	return nil
}

func (b *Builder) swigVersionCheck() error {
	swigCheckOnce.Do(func() {
		swigCheck = b.swigDoVersionCheck()
	})
	return swigCheck
}

// 查找要传递给swig的-intgosize选项的值。
var (
	swigIntSizeOnce  sync.Once
	swigIntSize      string
	swigIntSizeError error
)

// 如果sizeof（int）<=32 
const swigIntSizeCode = `
package main
const i int = 1 << 32
`

// 为swig>=2.0.9的-intgosize选项
// 确定目标系统上int的大小，则此代码无法生成。只跑一次。
func (b *Builder) swigDoIntSize(objdir string) (intsize string, err error) {
	if cfg.BuildN {
		return "$INTBITS", nil
	}
	src := filepath.Join(b.WorkDir, "swig_intsize.go")
	if err = os.WriteFile(src, []byte(swigIntSizeCode), 0666); err != nil {
		return
	}
	srcs := []string{src}

	p := load.GoFilesPackage(context.TODO(), load.PackageOpts{}, srcs)

	if _, _, e := BuildToolchain.gc(b, &Action{Mode: "swigDoIntSize", Package: p, Objdir: objdir}, "", nil, nil, "", false, srcs); e != nil {
		return "32", nil
	}
	return "64", nil
}

// 为swig>=2.0.9的-intgosize选项
// 确定目标系统上int的大小。
func (b *Builder) swigIntSize(objdir string) (intsize string, err error) {
	swigIntSizeOnce.Do(func() {
		swigIntSize, swigIntSizeError = b.swigDoIntSize(objdir)
	})
	return swigIntSize, swigIntSizeError
}

// 在一个SWIG输入文件上运行SWIG。
func (b *Builder) swigOne(a *Action, p *load.Package, file, objdir string, pcCFLAGS []string, cxx bool, intgosize string) (outGo, outC string, err error) {
	cgoCPPFLAGS, cgoCFLAGS, cgoCXXFLAGS, _, _, err := b.CFlags(p)
	if err != nil {
		return "", "", err
	}

	var cflags []string
	if cxx {
		cflags = str.StringList(cgoCPPFLAGS, pcCFLAGS, cgoCXXFLAGS)
	} else {
		cflags = str.StringList(cgoCPPFLAGS, pcCFLAGS, cgoCFLAGS)
	}

	n := 5 // 长度为“.swig”
	if cxx {
		n = 8 // 长度为“.swigxx”
	}
	base := file[:len(file)-n]
	goFile := base + ".go"
	gccBase := base + "_wrap."
	gccExt := "c"
	if cxx {
		gccExt = "cxx"
	}

	gccgo := cfg.BuildToolchainName == "gccgo"

	// swig 
	args := []string{
		"-go",
		"-cgo",
		"-intgosize", intgosize,
		"-module", base,
		"-o", objdir + gccBase + gccExt,
		"-outdir", objdir,
	}

	for _, f := range cflags {
		if len(f) > 3 && f[:2] == "-I" {
			args = append(args, f)
		}
	}

	if gccgo {
		args = append(args, "-gccgo")
		if pkgpath := gccgoPkgpath(p); pkgpath != "" {
			args = append(args, "-go-pkgpath", pkgpath)
		}
	}
	if cxx {
		args = append(args, "-c++")
	}

	out, err := b.runOut(a, p.Dir, nil, "swig", args, file)
	if err != nil {
		if len(out) > 0 {
			if bytes.Contains(out, []byte("-intgosize")) || bytes.Contains(out, []byte("-cgo")) {
				return "", "", errors.New("must have SWIG version >= 3.0.6")
			}
			b.showOutput(a, p.Dir, p.Desc(), b.processOutput(out)) // swig错误
			return "", "", errPrintedOutput
		}
		return "", "", err
	}
	if len(out) > 0 {
		b.showOutput(a, p.Dir, p.Desc(), b.processOutput(out)) // swig警告
	}

	// 如果输入为x.swig，则输出为objdir中的x.go。
	// 但是原始目录中可能也有一个x.go，如果它
	// 也使用cgo，cgo将同时处理这两个命令，并将其转换为x.cgo1。进入objdir，覆盖一个。
	// 重命名x。转到x_swig。去避免这个问题。
	// 我们忽略原始目录中以下划线
	// so_x_swig开头的文件。go不能与我们要编译的原始文件
	// 冲突。
	goFile = objdir + goFile
	newGoFile := objdir + "_" + base + "_swig.go"
	if err := os.Rename(goFile, newGoFile); err != nil {
		return "", "", err
	}
	return newGoFile, objdir + gccBase + gccExt, nil
}

// disableBuildID在创建对象文件而不是可执行文件或
// 构建ID。有些系统，比如Ubuntu，总是在每个链接中添加构建id，但我们不希望在生成对象文件时使用构建id。在其中一些系统上，一个普通的-r（不是
// 共享库时，调整链接器命令行以避免创建
// /-Wl，-r）将关闭--build id，但clang 3.0不支持
// plain-r。我不知道在使用clang 
// 时如何关闭--build id，除了传递一个尾随的--build id=none。这就是我们
// 所做的，但仅限于可能支持它的系统，也就是说，
// 通常使用黄金或GNU链接器的系统。
func (b *Builder) disableBuildID(ldflags []string) []string {
	switch cfg.Goos {
	case "android", "dragonfly", "linux", "netbsd":
		ldflags = append(ldflags, "-Wl,--build-id=none")
	}
	return ldflags
}

// mkAbsFiles将文件转换为绝对文件列表，
// 假设它们最初是相对于dir的，
// 并返回新列表。
func mkAbsFiles(dir string, files []string) []string {
	abs := make([]string, len(files))
	for i, f := range files {
		if !filepath.IsAbs(f) {
			f = filepath.Join(dir, f)
		}
		abs[i] = f
	}
	return abs
}

// passLongArgsInResponseFiles修改cmd，以便对于
// 某些程序，长参数在“响应文件”中传递，即磁盘上带有参数的
// 文件，每行一个参数。实际的
// 以@开头的参数意味着该参数的其余部分是
// 要展开的参数的文件名。
// 
// 参见18468期（Windows）和37768期（达尔文）。
func passLongArgsInResponseFiles(cmd *exec.Cmd) (cleanup func()) {
	cleanup = func() {} // 默认情况下不清理

	var argLen int
	for _, arg := range cmd.Args {
		argLen += len(arg)
	}

	// 如果我们没有接近32KB的args，只需正常传递args即可。
	// （使用30KB来保守；不确定如何计算）
	if !useResponseFile(cmd.Path, argLen) {
		return
	}

	tf, err := os.CreateTemp("", "args")
	if err != nil {
		log.Fatalf("error writing long arguments to response file: %v", err)
	}
	cleanup = func() { os.Remove(tf.Name()) }
	var buf bytes.Buffer
	for _, arg := range cmd.Args[1:] {
		fmt.Fprintf(&buf, "%s\n", encodeArg(arg))
	}
	if _, err := tf.Write(buf.Bytes()); err != nil {
		tf.Close()
		cleanup()
		log.Fatalf("error writing long arguments to response file: %v", err)
	}
	if err := tf.Close(); err != nil {
		cleanup()
		log.Fatalf("error writing long arguments to response file: %v", err)
	}
	cmd.Args = []string{cmd.Args[0], "@" + tf.Name()}
	return cleanup
}

func useResponseFile(path string, argLen int) bool {
	// ，除非程序使用objabi。Flagparse理解
	// 响应文件，不使用响应文件。
	// TODO:我们需要更多命令吗？asm？cgo？目前，no.
	prog := strings.TrimSuffix(filepath.Base(path), ".exe")
	switch prog {
	case "compile", "link":
	default:
		return false
	}

	if argLen > sys.ExecArgLengthLimit {
		return true
	}

	// 在运行的构建系统中，使用大约10%的
	// 时间的响应文件，只是为了练习这个代码路径。
	isBuilder := os.Getenv("GO_BUILDER_NAME") != ""
	if isBuilder && rand.Intn(10) == 0 {
		return true
	}

	return false
}

// encodeArg对响应文件写入的参数进行编码。
func encodeArg(arg string) string {
	// 如果没有需要重新编码的字符，请快速退出。
	if !strings.ContainsAny(arg, "\\\n") {
		return arg
	}
	var b strings.Builder
	for _, r := range arg {
		switch r {
		case '\\':
			b.WriteByte('\\')
			b.WriteByte('\\')
		case '\n':
			b.WriteByte('\\')
			b.WriteByte('n')
		default:
			b.WriteRune(r)
		}
	}
	return b.String()
}
